diff options
180 files changed, 1233 insertions, 791 deletions
diff --git a/.travis.yml b/.travis.yml index c648bd2ca7..5d4a9e9c67 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,11 @@ script: 'ci/travis.rb' before_install: - gem install bundler - "rm ${BUNDLE_GEMFILE}.lock" + - curl -L https://github.com/kr/beanstalkd/archive/v1.10.tar.gz | tar xz -C /tmp + - cd /tmp/beanstalkd-1.10/ + - make + - ./beanstalkd & + - cd $TRAVIS_BUILD_DIR before_script: - bundle update cache: bundler @@ -17,7 +17,7 @@ gem 'mocha', '~> 0.14', require: false gem 'rack-cache', '~> 1.2' gem 'jquery-rails', github: 'rails/jquery-rails', branch: 'master' gem 'coffee-rails', '~> 4.1.0' -gem 'turbolinks' +gem 'turbolinks', github: 'rails/turbolinks', branch: 'master' gem 'arel', github: 'rails/arel', branch: 'master' gem 'mail', github: 'mikel/mail', branch: 'master' @@ -79,7 +79,7 @@ group :test do end platforms :ruby do - gem 'nokogiri', '>= 1.4.5' + gem 'nokogiri', '>= 1.6.7.rc3' # Needed for compiling the ActionDispatch::Journey parser gem 'racc', '>=1.4.6', require: false @@ -127,3 +127,4 @@ end # A gem necessary for ActiveRecord tests with IBM DB gem 'ibm_db' if ENV['IBM_DB'] +gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] diff --git a/Gemfile.lock b/Gemfile.lock index 44fedddc04..563a9f56be 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -29,7 +29,7 @@ GIT GIT remote: git://github.com/rack/rack.git - revision: 4224c028c71c4ccca7cdb3e5a10c51af797a4d4d + revision: c28f271d0c91f45e13bfa8f07bed445ef91f41de branch: master specs: rack (2.0.0.alpha) @@ -37,14 +37,14 @@ GIT GIT remote: git://github.com/rails/arel.git - revision: d5432b4616ff43fbb14540d351eed351e21bb20e + revision: 77ec13b46af2926bfcfc3073685711c874b0d272 branch: master specs: arel (7.0.0.alpha) GIT remote: git://github.com/rails/globalid.git - revision: 8178ff2dc898a8f49dd71f6eb46f2a09712462de + revision: 1d8fca667740570d204fd955a0bd39ac539bac7f branch: master specs: globalid (0.3.6) @@ -52,7 +52,7 @@ GIT GIT remote: git://github.com/rails/jquery-rails.git - revision: 01cb69b17083c8eb8daba13a8daba4907b1c3813 + revision: 04fcfa29b859eef9479f89b6a799d00212902385 branch: master specs: jquery-rails (4.0.5) @@ -62,18 +62,18 @@ GIT GIT remote: git://github.com/rails/sass-rails.git - revision: 805cb17722b8a13ff00dffe20283a6ba2c9a45dc + revision: a3b25261a3d31ed9ff5dd6e841b777790fc86c55 branch: master specs: sass-rails (6.0.0) railties (>= 4.0.0, < 5.0) - sass (~> 3.3) + sass (~> 3.4) sprockets (>= 4.0) sprockets-rails (< 4.0) GIT remote: git://github.com/rails/sprockets-rails.git - revision: e65e088575be4f6b994823b80a277affb0a57a5e + revision: 600f981fe79ff2d4179baf84bd18fac5acb58b5e branch: master specs: sprockets-rails (3.0.0.beta2) @@ -83,11 +83,19 @@ GIT GIT remote: git://github.com/rails/sprockets.git - revision: 408e4ad79df056f6f2199e495782363c22a95c04 + revision: edae5cdfa241b0fb0fcb756b25cd561c3d8b7f29 branch: master specs: sprockets (4.0.0) - rack (~> 2.x) + rack (> 1, < 3) + +GIT + remote: git://github.com/rails/turbolinks.git + revision: 4bb563cd777875d3ad73cf007c26334f2aa8dc37 + branch: master + specs: + turbolinks (3.0.0) + coffee-rails PATH remote: . @@ -122,12 +130,11 @@ PATH activesupport (= 5.0.0.alpha) arel (= 7.0.0.alpha) activesupport (5.0.0.alpha) - concurrent-ruby (~> 0.9.1) + concurrent-ruby (~> 1.0.0.pre2, < 2.0.0) i18n (~> 0.7) json (~> 1.7, >= 1.7.7) method_source minitest (~> 5.1) - thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) rails (5.0.0.alpha) actionmailer (= 5.0.0.alpha) @@ -151,7 +158,7 @@ GEM remote: https://rubygems.org/ specs: amq-protocol (2.0.0) - backburner (1.0.0) + backburner (1.1.0) beaneater (~> 1.0) dante (> 0.1.5) bcrypt (3.1.10) @@ -160,8 +167,8 @@ GEM beaneater (1.0.0) benchmark-ips (2.3.0) builder (3.2.2) - bunny (2.0.0) - amq-protocol (>= 1.9.2) + bunny (2.2.0) + amq-protocol (>= 2.0.0) byebug (6.0.2) celluloid (0.16.0) timers (~> 4.0.0) @@ -172,7 +179,7 @@ GEM coffee-script-source execjs coffee-script-source (1.9.1.1) - concurrent-ruby (0.9.1) + concurrent-ruby (1.0.0.pre2) connection_pool (2.2.0) dalli (2.7.4) dante (0.2.0) @@ -183,8 +190,8 @@ GEM delayed_job (>= 3.0, < 4.1) erubis (2.7.0) execjs (2.6.0) - hitimes (1.2.2) - hitimes (1.2.2-x86-mingw32) + hitimes (1.2.3) + hitimes (1.2.3-x86-mingw32) i18n (0.7.0) json (1.8.3) kindlerb (0.1.1) @@ -194,8 +201,8 @@ GEM nokogiri (>= 1.5.9) metaclass (0.0.4) method_source (0.8.2) - mime-types (2.6.1) - mini_portile (0.6.2) + mime-types (2.6.2) + mini_portile (0.7.0.rc4) minitest (5.3.3) mocha (0.14.0) metaclass (~> 0.0.1) @@ -204,15 +211,15 @@ GEM mustache (1.0.2) mysql (2.9.1) mysql2 (0.4.0) - nokogiri (1.6.6.2) - mini_portile (~> 0.6.0) - nokogiri (1.6.6.2-x64-mingw32) - mini_portile (~> 0.6.0) - nokogiri (1.6.6.2-x86-mingw32) - mini_portile (~> 0.6.0) - pg (0.18.2) + nokogiri (1.6.7.rc3) + mini_portile (~> 0.7.0.rc4) + nokogiri (1.6.7.rc3-x64-mingw32) + mini_portile (~> 0.7.0.rc4) + nokogiri (1.6.7.rc3-x86-mingw32) + mini_portile (~> 0.7.0.rc4) + pg (0.18.3) psych (2.0.15) - que (0.10.0) + que (0.11.2) racc (1.4.12) rack-cache (1.2) rack (>= 0.4) @@ -243,12 +250,12 @@ GEM redis (~> 3.0) resque (~> 1.25) rufus-scheduler (~> 3.0) - rufus-scheduler (3.1.3) - sass (3.4.17) + rufus-scheduler (3.1.4) + sass (3.4.18) sdoc (0.4.1) json (~> 1.7, >= 1.7.7) rdoc (~> 4.0) - sequel (4.25.0) + sequel (4.26.0) serverengine (1.5.10) sigdump (~> 0.2.2) sidekiq (3.4.2) @@ -260,8 +267,8 @@ GEM sigdump (0.2.3) sinatra (1.0) rack (>= 1.0) - sneakers (1.1.1) - bunny (>= 1.7.0, <= 2.0.0) + sneakers (2.2.0) + bunny (~> 2.2.0) serverengine (~> 1.5.5) thor thread (~> 0.1.7) @@ -272,13 +279,13 @@ GEM thor (0.19.1) thread (0.1.7) thread_safe (0.3.5) - timers (4.0.1) + timers (4.0.4) hitimes - turbolinks (2.5.3) - coffee-rails tzinfo (1.2.2) thread_safe (~> 0.1) - uglifier (2.7.1) + tzinfo-data (1.2015.6) + tzinfo (>= 1.0.0) + uglifier (2.7.2) execjs (>= 0.3.0) json (>= 1.8.0) vegas (0.1.11) @@ -314,7 +321,7 @@ DEPENDENCIES mocha (~> 0.14) mysql (>= 2.9.0) mysql2 (>= 0.4.0) - nokogiri (>= 1.4.5) + nokogiri (>= 1.6.7.rc3) pg (>= 0.18.0) psych (~> 2.0) qu-rails! @@ -340,7 +347,8 @@ DEPENDENCIES sqlite3 (~> 1.3.6) stackprof sucker_punch - turbolinks + turbolinks! + tzinfo-data uglifier (>= 1.3.0) w3c_validators diff --git a/actionmailer/lib/action_mailer.rb b/actionmailer/lib/action_mailer.rb index 291a8c1e34..312dd1997c 100644 --- a/actionmailer/lib/action_mailer.rb +++ b/actionmailer/lib/action_mailer.rb @@ -49,3 +49,10 @@ module ActionMailer autoload :MessageDelivery autoload :DeliveryJob end + +autoload :Mime, 'action_dispatch/http/mime_type' + +ActiveSupport.on_load(:action_view) do + ActionView::Base.default_formats ||= Mime::SET.symbols + ActionView::Template::Types.delegate_to Mime +end diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb index 6b2e6a531c..50f2c71737 100644 --- a/actionmailer/test/base_test.rb +++ b/actionmailer/test/base_test.rb @@ -449,6 +449,13 @@ class BaseTest < ActiveSupport::TestCase assert_equal("Format with any!", email.parts[1].body.encoded) end + test 'explicit without specifying format with format.any' do + error = assert_raises(ArgumentError) do + BaseMailer.explicit_without_specifying_format_with_any.parts + end + assert_equal "You have to supply at least one format", error.message + end + test "explicit multipart with format(Hash)" do email = BaseMailer.explicit_multipart_with_options(true) email.ready_to_send! diff --git a/actionmailer/test/mailers/base_mailer.rb b/actionmailer/test/mailers/base_mailer.rb index bd991e209e..8c2225ce60 100644 --- a/actionmailer/test/mailers/base_mailer.rb +++ b/actionmailer/test/mailers/base_mailer.rb @@ -80,6 +80,12 @@ class BaseMailer < ActionMailer::Base end end + def explicit_without_specifying_format_with_any(hash = {}) + mail(hash) do |format| + format.any + end + end + def explicit_multipart_with_options(include_html = false) mail do |format| format.text(content_transfer_encoding: "base64"){ render "welcome" } diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 0727bb8369..04e5922ce8 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -248,6 +248,7 @@ module ActionController MODULES.each do |mod| include mod end + setup_renderer! # Define some internal variables that should not be propagated to the view. PROTECTED_IVARS = AbstractController::Rendering::DEFAULT_PROTECTED_INSTANCE_VARIABLES + [ diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb index 030a1f3478..0384740fef 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -140,13 +140,6 @@ module ActionController 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 diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb index 4214399b6f..00b551af94 100644 --- a/actionpack/lib/action_controller/metal/rendering.rb +++ b/actionpack/lib/action_controller/metal/rendering.rb @@ -11,10 +11,17 @@ module ActionController # Documentation at ActionController::Renderer#render delegate :render, to: :renderer - # Returns a renderer class (inherited from ActionController::Renderer) + # Returns a renderer instance (inherited from ActionController::Renderer) # for the controller. - def renderer - @renderer ||= Renderer.for(self) + attr_reader :renderer + + def setup_renderer! # :nodoc: + @renderer = Renderer.for(self) + end + + def inherited(klass) + klass.setup_renderer! + super end end diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index e5f3cb8e8d..5674eef67b 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -137,8 +137,8 @@ module ActionController #:nodoc: def handle_unverified_request request = @controller.request request.session = NullSessionHash.new(request) - request.env['action_dispatch.request.flash_hash'] = nil - request.env['rack.session.options'] = { skip: true } + request.flash = nil + request.session_options = { skip: true } request.cookie_jar = NullCookieJar.build(request, {}) end diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb index bf5c7003ff..903dba3eb4 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -97,9 +97,8 @@ module ActionController # environment they should only be set once at boot-time and never mutated at # runtime. # - # <tt>ActionController::Parameters</tt> inherits from - # <tt>ActiveSupport::HashWithIndifferentAccess</tt>, this means - # that you can fetch values using either <tt>:key</tt> or <tt>"key"</tt>. + # You can fetch values of <tt>ActionController::Parameters</tt> using either + # <tt>:key</tt> or <tt>"key"</tt>. # # params = ActionController::Parameters.new(key: 'value') # params[:key] # => "value" diff --git a/actionpack/lib/action_controller/renderer.rb b/actionpack/lib/action_controller/renderer.rb index e8b29c5b5e..e4d19e9dba 100644 --- a/actionpack/lib/action_controller/renderer.rb +++ b/actionpack/lib/action_controller/renderer.rb @@ -34,67 +34,78 @@ module ActionController # ApplicationController.renderer.new(method: 'post', https: true) # class Renderer - class_attribute :controller, :defaults - # Rack environment to render templates in. - attr_reader :env + attr_reader :defaults, :controller - class << self - delegate :render, to: :new + DEFAULTS = { + http_host: 'example.org', + https: false, + method: 'get', + script_name: '', + input: '' + }.freeze - # Create a new renderer class for a specific controller class. - def for(controller) - Class.new self do - self.controller = controller - self.defaults = { - http_host: 'example.org', - https: false, - method: 'get', - script_name: '', - 'rack.input' => '' - } - end - end + # Create a new renderer instance for a specific controller class. + def self.for(controller, env = {}, defaults = DEFAULTS) + new(controller, env, defaults) + end + + # Create a new renderer for the same controller but with a new env. + def new(env = {}) + self.class.new controller, env, defaults + end + + # Create a new renderer for the same controller but with new defaults. + def with_defaults(defaults) + self.class.new controller, env, self.defaults.merge(defaults) end # Accepts a custom Rack environment to render templates in. # It will be merged with ActionController::Renderer.defaults - def initialize(env = {}) - @env = normalize_keys(defaults).merge normalize_keys(env) - @env['action_dispatch.routes'] = controller._routes + def initialize(controller, env, defaults) + @controller = controller + @defaults = defaults + @env = normalize_keys defaults.merge(env) end # Render templates with any options from ActionController::Base#render_to_string. def render(*args) - raise 'missing controller' unless controller? + raise 'missing controller' unless controller - instance = controller.build_with_env(env) + request = ActionDispatch::Request.new @env + request.routes = controller._routes + + instance = controller.new + instance.set_request! request + instance.set_response! controller.make_response!(request) instance.render_to_string(*args) end private def normalize_keys(env) - http_header_format(env).tap do |new_env| - handle_method_key! new_env - handle_https_key! new_env - end + new_env = {} + env.each_pair { |k,v| new_env[rack_key_for(k)] = rack_value_for(k, v) } + new_env end - def http_header_format(env) - env.transform_keys do |key| - key.is_a?(Symbol) ? key.to_s.upcase : key - end - end + RACK_KEY_TRANSLATION = { + http_host: 'HTTP_HOST', + https: 'HTTPS', + method: 'REQUEST_METHOD', + script_name: 'SCRIPT_NAME', + input: 'rack.input' + } - def handle_method_key!(env) - if method = env.delete('METHOD') - env['REQUEST_METHOD'] = method.upcase - end - end + IDENTITY = ->(_) { _ } + + RACK_VALUE_TRANSLATION = { + https: ->(v) { v ? 'on' : 'off' }, + method: ->(v) { v.upcase }, + } + + def rack_key_for(key); RACK_KEY_TRANSLATION[key]; end - def handle_https_key!(env) - if env.has_key? 'HTTPS' - env['HTTPS'] = env['HTTPS'] ? 'on' : 'off' - end + def rack_value_for(key, value) + RACK_VALUE_TRANSLATION.fetch(key, IDENTITY).call value end end end diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index fbbaa1a887..f16c851456 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -33,6 +33,9 @@ module ActionController self.session = session self.session_options = TestSession::DEFAULT_OPTIONS + @custom_param_parsers = { + Mime::XML => lambda { |raw_post| Hash.from_xml(raw_post)['hash'] } + } end def query_string=(string) @@ -74,26 +77,18 @@ module ActionController 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 - # when we switch to caling `call` on the controller - case content_mime_type.to_sym when nil raise "Unknown Content-Type: #{content_type}" when :json data = ActiveSupport::JSON.encode(non_path_parameters) - params = ActiveSupport::JSON.decode(data).with_indifferent_access - self.request_parameters = params when :xml data = non_path_parameters.to_xml - params = Hash.from_xml(data)['hash'] - self.request_parameters = params when :url_encoded_form data = non_path_parameters.to_query else + @custom_param_parsers[content_mime_type] = ->(_) { non_path_parameters } data = non_path_parameters.to_query - self.request_parameters = non_path_parameters end end @@ -136,6 +131,12 @@ module ActionController "multipart/form-data; boundary=#{Rack::Test::MULTIPART_BOUNDARY}" end end.new + + private + + def params_parsers + super.merge @custom_param_parsers + end end class LiveTestResponse < Live::Response diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb index 3c9f8cd9e4..27fadb708e 100644 --- a/actionpack/lib/action_dispatch/http/parameters.rb +++ b/actionpack/lib/action_dispatch/http/parameters.rb @@ -3,6 +3,20 @@ module ActionDispatch module Parameters PARAMETERS_KEY = 'action_dispatch.request.path_parameters' + DEFAULT_PARSERS = { + Mime::JSON => lambda { |raw_post| + data = ActiveSupport::JSON.decode(raw_post) + data.is_a?(Hash) ? data : {:_json => data} + } + } + + def self.included(klass) + class << klass + attr_accessor :parameter_parsers + end + + klass.parameter_parsers = DEFAULT_PARSERS + end # Returns both GET and POST \parameters in a single hash. def parameters params = get_header("action_dispatch.request.parameters") @@ -31,6 +45,27 @@ module ActionDispatch def path_parameters get_header(PARAMETERS_KEY) || {} end + + private + + def parse_formatted_parameters(parsers) + return yield if content_length.zero? + + strategy = parsers.fetch(content_mime_type) { return yield } + + begin + strategy.call(raw_post) + rescue => e # JSON or Ruby code block errors + my_logger = logger || ActiveSupport::Logger.new($stderr) + my_logger.debug "Error occurred while parsing request parameters.\nContents:\n\n#{raw_post}" + + raise ParamsParser::ParseError.new(e.message, e) + end + end + + def params_parsers + ActionDispatch::Request.parameter_parsers + end end end end diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 18504eba6d..eaa7e88b34 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -324,7 +324,7 @@ module ActionDispatch else self.session = {} end - set_header('action_dispatch.request.flash_hash', nil) + self.flash = nil end def session=(session) #:nodoc: @@ -348,8 +348,14 @@ module ActionDispatch # Override Rack's POST method to support indifferent access def POST fetch_header("action_dispatch.request.request_parameters") do - self.request_parameters = Request::Utils.normalize_encode_params(super || {}) + pr = parse_formatted_parameters(params_parsers) do |params| + super || {} + end + self.request_parameters = Request::Utils.normalize_encode_params(pr) end + rescue ParamsParser::ParseError # one of the parse strategies blew up + self.request_parameters = Request::Utils.normalize_encode_params(super || {}) + raise rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e raise ActionController::BadRequest.new(:request, e) end diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index d1e1f1fcf6..45ffacd6f5 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -402,7 +402,7 @@ module ActionDispatch # :nodoc: end def rack_response(status, header) - if NO_CONTENT_CODES.include?(@status) + if NO_CONTENT_CODES.include?(status) header.delete CONTENT_TYPE header.delete 'Content-Length' [status, header, []] diff --git a/actionpack/lib/action_dispatch/journey/nfa/dot.rb b/actionpack/lib/action_dispatch/journey/nfa/dot.rb index 47bf76bdbf..7063b44bb5 100644 --- a/actionpack/lib/action_dispatch/journey/nfa/dot.rb +++ b/actionpack/lib/action_dispatch/journey/nfa/dot.rb @@ -1,5 +1,3 @@ -# encoding: utf-8 - module ActionDispatch module Journey # :nodoc: module NFA # :nodoc: diff --git a/actionpack/lib/action_dispatch/journey/visitors.rb b/actionpack/lib/action_dispatch/journey/visitors.rb index 537c9b2f5c..306d2e674a 100644 --- a/actionpack/lib/action_dispatch/journey/visitors.rb +++ b/actionpack/lib/action_dispatch/journey/visitors.rb @@ -1,5 +1,3 @@ -# encoding: utf-8 - module ActionDispatch module Journey # :nodoc: class Format diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb index 039efc3af8..5fd984cd07 100644 --- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb +++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb @@ -61,7 +61,7 @@ module ActionDispatch end def traces - appplication_trace_with_ids = [] + application_trace_with_ids = [] framework_trace_with_ids = [] full_trace_with_ids = [] @@ -69,7 +69,7 @@ module ActionDispatch trace_with_id = { id: idx, trace: trace } if application_trace.include?(trace) - appplication_trace_with_ids << trace_with_id + application_trace_with_ids << trace_with_id else framework_trace_with_ids << trace_with_id end @@ -78,7 +78,7 @@ module ActionDispatch end { - "Application Trace" => appplication_trace_with_ids, + "Application Trace" => application_trace_with_ids, "Framework Trace" => framework_trace_with_ids, "Full Trace" => full_trace_with_ids } diff --git a/actionpack/lib/action_dispatch/middleware/params_parser.rb b/actionpack/lib/action_dispatch/middleware/params_parser.rb index 9cde9c9b98..18af0a583a 100644 --- a/actionpack/lib/action_dispatch/middleware/params_parser.rb +++ b/actionpack/lib/action_dispatch/middleware/params_parser.rb @@ -18,48 +18,13 @@ module ActionDispatch end end - DEFAULT_PARSERS = { - Mime::JSON => lambda { |raw_post| - data = ActiveSupport::JSON.decode(raw_post) - data = {:_json => data} unless data.is_a?(Hash) - Request::Utils.normalize_encode_params(data) - } - } - # Create a new +ParamsParser+ middleware instance. # # The +parsers+ argument can take Hash of parsers where key is identifying # content mime type, and value is a lambda that is going to process data. - def initialize(app, parsers = {}) - @app, @parsers = app, DEFAULT_PARSERS.merge(parsers) + def self.new(app, parsers = {}) + ActionDispatch::Request.parameter_parsers = ActionDispatch::Request::DEFAULT_PARSERS.merge(parsers) + app end - - def call(env) - request = Request.new(env) - - parse_formatted_parameters(request, @parsers) do |params| - request.request_parameters = params - end - - @app.call(env) - end - - private - def parse_formatted_parameters(request, parsers) - return if request.content_length.zero? - - strategy = parsers.fetch(request.content_mime_type) { return nil } - - 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}" - - raise ParseError.new(e.message, e) - end - - def logger(request) - request.logger || ActiveSupport::Logger.new($stderr) - end end end diff --git a/actionpack/lib/action_dispatch/middleware/ssl.rb b/actionpack/lib/action_dispatch/middleware/ssl.rb index b72953f1d1..47f475559a 100644 --- a/actionpack/lib/action_dispatch/middleware/ssl.rb +++ b/actionpack/lib/action_dispatch/middleware/ssl.rb @@ -15,7 +15,8 @@ module ActionDispatch # # Configure HSTS with `hsts: { … }`: # * `expires`: How long, in seconds, these settings will stick. Defaults to - # `18.weeks`, the minimum required to qualify for browser preload lists. + # `180.days` (recommended). The minimum required to qualify for browser + # preload lists is `18.weeks`. # * `subdomains`: Set to `true` to tell the browser to apply these settings # to all subdomains. This protects your cookies from interception by a # vulnerable site on a subdomain. Defaults to `false`. diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb index 8757c9ea7f..f3c6be864f 100644 --- a/actionpack/lib/action_dispatch/routing.rb +++ b/actionpack/lib/action_dispatch/routing.rb @@ -1,5 +1,3 @@ -# encoding: UTF-8 - module ActionDispatch # The routing module provides URL rewriting in native Ruby. It's a way to # redirect incoming requests to controllers and actions. This replaces @@ -148,6 +146,7 @@ module ActionDispatch # get 'geocode/:postalcode' => :show, constraints: { # postalcode: /\d{5}(-\d{4})?/ # } + # end # # Constraints can include the 'ignorecase' and 'extended syntax' regular # expression modifiers: diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index 3c498960e4..ef7aab72c6 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -104,7 +104,6 @@ class ActionDispatch::IntegrationTest < ActiveSupport::TestCase middleware.use ActionDispatch::ShowExceptions, ActionDispatch::PublicExceptions.new("#{FIXTURE_LOAD_PATH}/public") middleware.use ActionDispatch::DebugExceptions middleware.use ActionDispatch::Callbacks - middleware.use ActionDispatch::ParamsParser middleware.use ActionDispatch::Cookies middleware.use ActionDispatch::Flash middleware.use Rack::Head diff --git a/actionpack/test/controller/renderer_test.rb b/actionpack/test/controller/renderer_test.rb index b55a25430b..16d24fa82a 100644 --- a/actionpack/test/controller/renderer_test.rb +++ b/actionpack/test/controller/renderer_test.rb @@ -1,6 +1,10 @@ require 'abstract_unit' class RendererTest < ActiveSupport::TestCase + test 'action controller base has a renderer' do + assert ActionController::Base.renderer + end + test 'creating with a controller' do controller = CommentsController renderer = ActionController::Renderer.for controller @@ -57,8 +61,7 @@ class RendererTest < ActiveSupport::TestCase end test 'rendering with defaults' do - renderer = ApplicationController.renderer - renderer.defaults[:https] = true + renderer = ApplicationController.renderer.new https: true content = renderer.render inline: '<%= request.ssl? %>' assert_equal 'true', content @@ -67,8 +70,8 @@ class RendererTest < ActiveSupport::TestCase test 'same defaults from the same controller' do renderer_defaults = ->(controller) { controller.renderer.defaults } - assert renderer_defaults[AccountsController].equal? renderer_defaults[AccountsController] - assert_not renderer_defaults[AccountsController].equal? renderer_defaults[CommentsController] + assert_equal renderer_defaults[AccountsController], renderer_defaults[AccountsController] + assert_equal renderer_defaults[AccountsController], renderer_defaults[CommentsController] end test 'rendering with different formats' do @@ -83,18 +86,6 @@ class RendererTest < ActiveSupport::TestCase test 'rendering with helpers' do assert_equal "<p>1\n<br />2</p>", render[inline: '<%= simple_format "1\n2" %>'] end - - test 'rendering from inherited renderer' do - inherited = Class.new ApplicationController.renderer do - defaults[:script_name] = 'script' - def render(options) - super options.merge(locals: { param: :value }) - end - end - - template = '<%= url_for controller: :foo, action: :bar, param: param %>' - assert_equal 'script/foo/bar?param=value', inherited.render(inline: template) - end private def render diff --git a/actionpack/test/controller/send_file_test.rb b/actionpack/test/controller/send_file_test.rb index c0ddcf7f50..fb9883fb18 100644 --- a/actionpack/test/controller/send_file_test.rb +++ b/actionpack/test/controller/send_file_test.rb @@ -143,7 +143,18 @@ class SendFileTest < ActionController::TestCase } @controller.headers = {} - assert_raise(ArgumentError) { @controller.send(:send_file_headers!, options) } + error = assert_raise(ArgumentError) { @controller.send(:send_file_headers!, options) } + assert_equal "Unknown MIME type #{options[:type]}", error.message + end + + def test_send_file_headers_with_nil_content_type + options = { + :type => nil + } + + @controller.headers = {} + error = assert_raise(ArgumentError) { @controller.send(:send_file_headers!, options) } + assert_equal ":type option required", error.message end def test_send_file_headers_guess_type_from_extension diff --git a/actionpack/test/controller/webservice_test.rb b/actionpack/test/controller/webservice_test.rb index b26f037c36..8b37c9599e 100644 --- a/actionpack/test/controller/webservice_test.rb +++ b/actionpack/test/controller/webservice_test.rb @@ -97,24 +97,28 @@ class WebServiceTest < ActionDispatch::IntegrationTest end def test_parsing_json_doesnot_rescue_exception - with_test_route_set do - with_params_parsers Mime::JSON => Proc.new { |data| raise Interrupt } do - assert_raises(Interrupt) do - post "/", - params: '{"title":"JSON"}}', - headers: { 'CONTENT_TYPE' => 'application/json' } - end + req = Class.new(ActionDispatch::Request) do + def params_parsers + { Mime::JSON => Proc.new { |data| raise Interrupt } } end + + def content_length; get_header('rack.input').length; end + end.new({ 'rack.input' => StringIO.new('{"title":"JSON"}}'), 'CONTENT_TYPE' => 'application/json' }) + + assert_raises(Interrupt) do + req.request_parameters end end private def with_params_parsers(parsers = {}) old_session = @integration_session - @app = ActionDispatch::ParamsParser.new(app.routes, parsers) + original_parsers = ActionDispatch::Request.parameter_parsers + ActionDispatch::Request.parameter_parsers = original_parsers.merge parsers reset! yield ensure + ActionDispatch::Request.parameter_parsers = original_parsers @integration_session = old_session end diff --git a/actionpack/test/journey/router_test.rb b/actionpack/test/journey/router_test.rb index d512dae4e7..6a3af32946 100644 --- a/actionpack/test/journey/router_test.rb +++ b/actionpack/test/journey/router_test.rb @@ -1,4 +1,3 @@ -# encoding: UTF-8 require 'abstract_unit' module ActionDispatch diff --git a/actionview/lib/action_view/dependency_tracker.rb b/actionview/lib/action_view/dependency_tracker.rb index 6e8c7f8203..7716955fd9 100644 --- a/actionview/lib/action_view/dependency_tracker.rb +++ b/actionview/lib/action_view/dependency_tracker.rb @@ -1,9 +1,9 @@ -require 'thread_safe' +require 'concurrent' require 'action_view/path_set' module ActionView class DependencyTracker # :nodoc: - @trackers = ThreadSafe::Cache.new + @trackers = Concurrent::Map.new def self.find_dependencies(name, template, view_paths = nil) tracker = @trackers[template.handler] diff --git a/actionview/lib/action_view/digestor.rb b/actionview/lib/action_view/digestor.rb index 9a8a4feb2e..12e9723a02 100644 --- a/actionview/lib/action_view/digestor.rb +++ b/actionview/lib/action_view/digestor.rb @@ -1,11 +1,11 @@ -require 'thread_safe' +require 'concurrent' require 'action_view/dependency_tracker' require 'monitor' module ActionView class Digestor cattr_reader(:cache) - @@cache = ThreadSafe::Cache.new + @@cache = Concurrent::Map.new @@digest_monitor = Monitor.new class PerRequestDigestCacheExpiry < Struct.new(:app) # :nodoc: @@ -28,7 +28,7 @@ module ActionView cache_key = ([ options[:name], options[:finder].details_key.hash ].compact + Array.wrap(options[:dependencies])).join('.') # this is a correctly done double-checked locking idiom - # (ThreadSafe::Cache's lookups have volatile semantics) + # (Concurrent::Map's lookups have volatile semantics) @@cache[cache_key] || @@digest_monitor.synchronize do @@cache.fetch(cache_key) do # re-check under lock compute_and_store_digest(cache_key, options) diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb index 3a9acafaa2..ebf5ea5b77 100644 --- a/actionview/lib/action_view/helpers/form_helper.rb +++ b/actionview/lib/action_view/helpers/form_helper.rb @@ -1880,7 +1880,7 @@ module ActionView # create: "Add %{model}" # # ==== Examples - # button("Create a post") + # button("Create post") # # => <button name='button' type='submit'>Create post</button> # # button do diff --git a/actionview/lib/action_view/helpers/number_helper.rb b/actionview/lib/action_view/helpers/number_helper.rb index 13effa592d..470dc86d6c 100644 --- a/actionview/lib/action_view/helpers/number_helper.rb +++ b/actionview/lib/action_view/helpers/number_helper.rb @@ -1,4 +1,3 @@ - require 'active_support/core_ext/hash/keys' require 'active_support/core_ext/string/output_safety' require 'active_support/number_helper' diff --git a/actionview/lib/action_view/helpers/translation_helper.rb b/actionview/lib/action_view/helpers/translation_helper.rb index 0615bd2e0d..dde1ef22ac 100644 --- a/actionview/lib/action_view/helpers/translation_helper.rb +++ b/actionview/lib/action_view/helpers/translation_helper.rb @@ -88,7 +88,14 @@ module ActionView raise e if raise_error keys = I18n.normalize_keys(e.locale, e.key, e.options[:scope]) - content_tag('span', keys.last.to_s.titleize, :class => 'translation_missing', :title => "translation missing: #{keys.join('.')}") + title = "translation missing: #{keys.join('.')}" + + interpolations = options.except(:default) + if interpolations.any? + title << ", " << interpolations.map { |k, v| "#{k}: #{ERB::Util.html_escape(v)}" }.join(', ') + end + + content_tag('span', keys.last.to_s.titleize, class: 'translation_missing', title: title) end end alias :t :translate diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb index 0d2153d41b..ec6edfaaa3 100644 --- a/actionview/lib/action_view/lookup_context.rb +++ b/actionview/lib/action_view/lookup_context.rb @@ -1,4 +1,4 @@ -require 'thread_safe' +require 'concurrent' require 'active_support/core_ext/module/remove_method' require 'active_support/core_ext/module/attribute_accessors' require 'action_view/template/resolver' @@ -62,7 +62,7 @@ module ActionView alias :object_hash :hash attr_reader :hash - @details_keys = ThreadSafe::Cache.new + @details_keys = Concurrent::Map.new def self.get(details) if details[:formats] diff --git a/actionview/lib/action_view/record_identifier.rb b/actionview/lib/action_view/record_identifier.rb index 6c6e69101b..4b44eb5520 100644 --- a/actionview/lib/action_view/record_identifier.rb +++ b/actionview/lib/action_view/record_identifier.rb @@ -11,7 +11,7 @@ module ActionView # <%= post.body %> # <% end %> # - # When +post+ is a new, unsaved ActiveRecord::Base intance, the resulting HTML + # When +post+ is a new, unsaved ActiveRecord::Base instance, the resulting HTML # is: # # <div id="new_post" class="post"> diff --git a/actionview/lib/action_view/renderer/partial_renderer.rb b/actionview/lib/action_view/renderer/partial_renderer.rb index 1f9e960488..39c8658ffe 100644 --- a/actionview/lib/action_view/renderer/partial_renderer.rb +++ b/actionview/lib/action_view/renderer/partial_renderer.rb @@ -1,5 +1,5 @@ require 'action_view/renderer/partial_renderer/collection_caching' -require 'thread_safe' +require 'concurrent' module ActionView class PartialIteration @@ -283,8 +283,8 @@ module ActionView class PartialRenderer < AbstractRenderer include CollectionCaching - PREFIXED_PARTIAL_NAMES = ThreadSafe::Cache.new do |h, k| - h[k] = ThreadSafe::Cache.new + PREFIXED_PARTIAL_NAMES = Concurrent::Map.new do |h, k| + h[k] = Concurrent::Map.new end def initialize(*) diff --git a/actionview/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb index 28967f40a6..7859c58b43 100644 --- a/actionview/lib/action_view/template/resolver.rb +++ b/actionview/lib/action_view/template/resolver.rb @@ -3,7 +3,7 @@ require "active_support/core_ext/class" require "active_support/core_ext/module/attribute_accessors" require "action_view/template" require "thread" -require "thread_safe" +require "concurrent" module ActionView # = Action View Resolver @@ -35,7 +35,7 @@ module ActionView # Threadsafe template cache class Cache #:nodoc: - class SmallCache < ThreadSafe::Cache + class SmallCache < Concurrent::Map def initialize(options = {}) super(options.merge(:initial_capacity => 2)) end diff --git a/actionview/test/abstract_unit.rb b/actionview/test/abstract_unit.rb index 12870acaa6..2354e91822 100644 --- a/actionview/test/abstract_unit.rb +++ b/actionview/test/abstract_unit.rb @@ -151,7 +151,6 @@ class ActionDispatch::IntegrationTest < ActiveSupport::TestCase middleware.use ActionDispatch::ShowExceptions, ActionDispatch::PublicExceptions.new("#{FIXTURE_LOAD_PATH}/public") middleware.use ActionDispatch::DebugExceptions middleware.use ActionDispatch::Callbacks - middleware.use ActionDispatch::ParamsParser middleware.use ActionDispatch::Cookies middleware.use ActionDispatch::Flash middleware.use Rack::Head diff --git a/actionview/test/actionpack/controller/capture_test.rb b/actionview/test/actionpack/controller/capture_test.rb index f8387b27b0..933456ce9d 100644 --- a/actionview/test/actionpack/controller/capture_test.rb +++ b/actionview/test/actionpack/controller/capture_test.rb @@ -54,7 +54,7 @@ class CaptureTest < ActionController::TestCase assert_equal expected_content_for_output, @response.body end - def test_should_concatentate_content_for + def test_should_concatenate_content_for get :content_for_concatenated assert_equal expected_content_for_output, @response.body end diff --git a/actionview/test/actionpack/controller/render_test.rb b/actionview/test/actionpack/controller/render_test.rb index 8d048ddbcb..27150c7d5f 100644 --- a/actionview/test/actionpack/controller/render_test.rb +++ b/actionview/test/actionpack/controller/render_test.rb @@ -1122,7 +1122,7 @@ class RenderTest < ActionController::TestCase assert_equal "<title>Putting stuff in the title!</title>\nGreat stuff!\n", @response.body end - def test_overwritting_rendering_relative_file_with_extension + def test_overwriting_rendering_relative_file_with_extension get :hello_world_from_rxml_using_template assert_equal "<html>\n <p>Hello</p>\n</html>\n", @response.body diff --git a/actionview/test/template/dependency_tracker_test.rb b/actionview/test/template/dependency_tracker_test.rb index 672b4747ec..3ece9e50cd 100644 --- a/actionview/test/template/dependency_tracker_test.rb +++ b/actionview/test/template/dependency_tracker_test.rb @@ -1,4 +1,3 @@ - require 'abstract_unit' require 'action_view/dependency_tracker' diff --git a/actionview/test/template/form_helper_test.rb b/actionview/test/template/form_helper_test.rb index aef137935a..77c660d450 100644 --- a/actionview/test/template/form_helper_test.rb +++ b/actionview/test/template/form_helper_test.rb @@ -366,7 +366,7 @@ class FormHelperTest < ActionView::TestCase ) end - def test_label_with_to_model_and_overriden_model_name + def test_label_with_to_model_and_overridden_model_name with_locale :label do assert_dom_equal( %{<label for="post_delegator_title">Delegate model_name title</label>}, diff --git a/actionview/test/template/number_helper_test.rb b/actionview/test/template/number_helper_test.rb index 631d45586b..ace3e950b8 100644 --- a/actionview/test/template/number_helper_test.rb +++ b/actionview/test/template/number_helper_test.rb @@ -1,4 +1,3 @@ -# encoding: utf-8 require "abstract_unit" class NumberHelperTest < ActionView::TestCase diff --git a/actionview/test/template/translation_helper_test.rb b/actionview/test/template/translation_helper_test.rb index 749d0dd7fd..261576bead 100644 --- a/actionview/test/template/translation_helper_test.rb +++ b/actionview/test/template/translation_helper_test.rb @@ -60,6 +60,12 @@ class TranslationHelperTest < ActiveSupport::TestCase assert_equal true, translate(:"translations.missing").html_safe? end + def test_returns_missing_translation_message_with_unescaped_interpolation + expected = '<span class="translation_missing" title="translation missing: en.translations.missing, name: Kir, year: 2015, vulnerable: &quot; onclick=&quot;alert()&quot;">Missing</span>' + assert_equal expected, translate(:"translations.missing", name: "Kir", year: "2015", vulnerable: %{" onclick="alert()"}) + assert translate(:"translations.missing").html_safe? + end + def test_raises_missing_translation_message_with_raise_config_option ActionView::Base.raise_on_missing_translations = true diff --git a/activejob/lib/active_job/async_job.rb b/activejob/lib/active_job/async_job.rb index 7fcffc4c24..6c1c070994 100644 --- a/activejob/lib/active_job/async_job.rb +++ b/activejob/lib/active_job/async_job.rb @@ -1,5 +1,4 @@ require 'concurrent' -require 'thread_safe' module ActiveJob # == Active Job Async Job @@ -31,7 +30,7 @@ module ActiveJob fallback_policy: :caller_runs # shouldn't matter -- 0 max queue }.freeze - QUEUES = ThreadSafe::Cache.new do |hash, queue_name| #:nodoc: + QUEUES = Concurrent::Map.new do |hash, queue_name| #:nodoc: hash.compute_if_absent(queue_name) { ActiveJob::AsyncJob.create_thread_pool } end diff --git a/activejob/test/cases/test_case_test.rb b/activejob/test/cases/test_case_test.rb index ee816e1dd5..616454a4b6 100644 --- a/activejob/test/cases/test_case_test.rb +++ b/activejob/test/cases/test_case_test.rb @@ -9,7 +9,7 @@ class ActiveJobTestCaseTest < ActiveJob::TestCase # the `class_attribute` inheritance class TestClassAttributeInheritanceJob < ActiveJob::Base def self.queue_adapter=(*) - raise 'Attemping to break `class_attribute` inheritance, bad!' + raise 'Attempting to break `class_attribute` inheritance, bad!' end end diff --git a/activejob/test/helper.rb b/activejob/test/helper.rb index 55f690bda8..7e86415f48 100644 --- a/activejob/test/helper.rb +++ b/activejob/test/helper.rb @@ -11,6 +11,7 @@ GlobalID.app = 'aj' if ENV['AJ_INTEGRATION_TESTS'] require 'support/integration/helper' else + ActiveJob::Base.logger = Logger.new(nil) require "adapters/#{@adapter}" end diff --git a/activejob/test/support/integration/adapters/sidekiq.rb b/activejob/test/support/integration/adapters/sidekiq.rb index d9938600f2..9aa07bcb52 100644 --- a/activejob/test/support/integration/adapters/sidekiq.rb +++ b/activejob/test/support/integration/adapters/sidekiq.rb @@ -58,7 +58,7 @@ module SidekiqJobsManager timeout: 1, }) Sidekiq.average_scheduled_poll_interval = 0.5 - Sidekiq::Scheduled.const_set :INITIAL_WAIT, 1 + Sidekiq.options[:poll_interval_average] = 1 begin sidekiq.run continue_write.puts "started" diff --git a/activejob/test/support/integration/dummy_app_template.rb b/activejob/test/support/integration/dummy_app_template.rb index 4ffdb8cffa..0c062a025e 100644 --- a/activejob/test/support/integration/dummy_app_template.rb +++ b/activejob/test/support/integration/dummy_app_template.rb @@ -1,8 +1,9 @@ if ENV['AJ_ADAPTER'] == 'delayed_job' generate "delayed_job:active_record", "--quiet" - rake("db:migrate") end +rake("db:migrate") + initializer 'activejob.rb', <<-CODE require "#{File.expand_path("../jobs_manager.rb", __FILE__)}" JobsManager.current_manager.setup diff --git a/activejob/test/support/integration/helper.rb b/activejob/test/support/integration/helper.rb index 8c2e5a86c2..4a1b0bfbcb 100644 --- a/activejob/test/support/integration/helper.rb +++ b/activejob/test/support/integration/helper.rb @@ -1,4 +1,4 @@ -puts "*** rake aj:integration:#{ENV['AJ_ADAPTER']} ***\n" +puts "\n\n*** rake aj:integration:#{ENV['AJ_ADAPTER']} ***\n" ENV["RAILS_ENV"] = "test" ActiveJob::Base.queue_name_prefix = nil diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb index 77e4ce3afe..1963a3fc4e 100644 --- a/activemodel/lib/active_model/attribute_methods.rb +++ b/activemodel/lib/active_model/attribute_methods.rb @@ -1,4 +1,4 @@ -require 'thread_safe' +require 'concurrent' require 'mutex_m' module ActiveModel @@ -350,7 +350,7 @@ module ActiveModel # significantly (in our case our test suite finishes 10% faster with # this cache). def attribute_method_matchers_cache #:nodoc: - @attribute_method_matchers_cache ||= ThreadSafe::Cache.new(initial_capacity: 4) + @attribute_method_matchers_cache ||= Concurrent::Map.new(initial_capacity: 4) end def attribute_method_matchers_matching(method_name) #:nodoc: diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index 29e0c977ce..4726a68f69 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - require 'active_support/core_ext/array/conversions' require 'active_support/core_ext/string/inflections' require 'active_support/core_ext/object/deep_dup' diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 554bec17d6..1b016b6e8b 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,42 @@ +* MySQL: support `unsigned` numeric data types. + + Example: + + create_table :foos do |t| + t.unsigned_integer :quantity + t.unsigned_bigint :total + t.unsigned_float :percentage + t.unsigned_decimal :price, precision: 10, scale: 2 + end + + The `unsigned: true` option may be used for the primary key: + + create_table :foos, id: :bigint, unsigned: true do |t| + … + end + + *Ryuta Kamizono* + +* Add `#views` and `#view_exists?` methods on connection adapters. + + *Ryuta Kamizono* + +* Correctly dump composite primary key. + + Example: + + create_table :barcodes, primary_key: ["region", "code"] do |t| + t.string :region + t.integer :code + end + + *Ryuta Kamizono* + +* Lookup the attribute name for `restrict_with_error` messages on the + model class that defines the association. + + *kuboon*, *Ronak Jangir* + * Correct query for PostgreSQL 8.2 compatibility. *Ben Murphy*, *Matthew Draper* diff --git a/activerecord/README.rdoc b/activerecord/README.rdoc index 049c5d2b3b..3eac8cc422 100644 --- a/activerecord/README.rdoc +++ b/activerecord/README.rdoc @@ -26,7 +26,7 @@ The Product class is automatically mapped to the table named "products", which might look like this: CREATE TABLE products ( - id int(11) NOT NULL auto_increment, + id int NOT NULL auto_increment, name varchar(255), PRIMARY KEY (id) ); diff --git a/activerecord/lib/active_record/aggregations.rb b/activerecord/lib/active_record/aggregations.rb index f7b50cd25a..a2aea63bdd 100644 --- a/activerecord/lib/active_record/aggregations.rb +++ b/activerecord/lib/active_record/aggregations.rb @@ -25,7 +25,7 @@ module ActiveRecord end # Active Record implements aggregation through a macro-like class method called +composed_of+ - # for representing attributes as value objects. It expresses relationships like "Account [is] + # for representing attributes as value objects. It expresses relationships like "Account [is] # composed of Money [among other things]" or "Person [is] composed of [an] address". Each call # to the macro adds a description of how the value objects are created from the attributes of # the entity object (when the entity is initialized either as a new object or from finding an diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 65eb6fba27..cf3e63b4a3 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -446,14 +446,14 @@ module ActiveRecord # The tables for these classes could look something like: # # CREATE TABLE users ( - # id int(11) NOT NULL auto_increment, - # account_id int(11) default NULL, + # id int NOT NULL auto_increment, + # account_id int default NULL, # name varchar default NULL, # PRIMARY KEY (id) # ) # # CREATE TABLE accounts ( - # id int(11) NOT NULL auto_increment, + # id int NOT NULL auto_increment, # name varchar default NULL, # PRIMARY KEY (id) # ) diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index f8211ef9fb..38bda0d2a5 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -15,7 +15,7 @@ module ActiveRecord when :restrict_with_error unless empty? - record = klass.human_attribute_name(reflection.name).downcase + record = owner.class.human_attribute_name(reflection.name).downcase message = owner.errors.generate_message(:base, :'restrict_dependent_destroy.many', record: record, raise: true) rescue nil if message ActiveSupport::Deprecation.warn(<<-MESSAGE.squish) diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb index 1829453d73..0fe9b2e81b 100644 --- a/activerecord/lib/active_record/associations/has_one_association.rb +++ b/activerecord/lib/active_record/associations/has_one_association.rb @@ -1,5 +1,5 @@ module ActiveRecord - # = Active Record Belongs To Has One Association + # = Active Record Has One Association module Associations class HasOneAssociation < SingularAssociation #:nodoc: include ForeignAssociation @@ -11,7 +11,7 @@ module ActiveRecord when :restrict_with_error if load_target - record = klass.human_attribute_name(reflection.name).downcase + record = owner.class.human_attribute_name(reflection.name).downcase message = owner.errors.generate_message(:base, :'restrict_dependent_destroy.one', record: record, raise: true) rescue nil if message ActiveSupport::Deprecation.warn(<<-MESSAGE.squish) diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 0862306749..82b07de482 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -1,7 +1,7 @@ require 'active_support/core_ext/enumerable' require 'active_support/core_ext/string/filters' require 'mutex_m' -require 'thread_safe' +require 'concurrent' module ActiveRecord # = Active Record Attribute Methods @@ -37,7 +37,7 @@ module ActiveRecord class AttributeMethodCache def initialize @module = Module.new - @method_cache = ThreadSafe::Cache.new + @method_cache = Concurrent::Map.new end def [](name) diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb index 07d5e7d38e..bbf2a51a0e 100644 --- a/activerecord/lib/active_record/attribute_methods/write.rb +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -45,7 +45,7 @@ module ActiveRecord write_attribute_with_type_cast(attr_name, value, true) end - def raw_write_attribute(attr_name, value) + def raw_write_attribute(attr_name, value) # :nodoc: write_attribute_with_type_cast(attr_name, value, false) end diff --git a/activerecord/lib/active_record/attribute_set/builder.rb b/activerecord/lib/active_record/attribute_set/builder.rb index e85777c335..0c730d313f 100644 --- a/activerecord/lib/active_record/attribute_set/builder.rb +++ b/activerecord/lib/active_record/attribute_set/builder.rb @@ -1,3 +1,5 @@ +require 'active_record/attribute' + module ActiveRecord class AttributeSet # :nodoc: class Builder # :nodoc: diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index 282af220fb..b579bc1e93 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -1,5 +1,5 @@ require 'thread' -require 'thread_safe' +require 'concurrent' require 'monitor' module ActiveRecord @@ -337,7 +337,7 @@ module ActiveRecord # that case +conn.owner+ attr should be consulted. # Access and modification of +@thread_cached_conns+ does not require # synchronization. - @thread_cached_conns = ThreadSafe::Cache.new(:initial_capacity => @size) + @thread_cached_conns = Concurrent::Map.new(:initial_capacity => @size) @connections = [] @automatic_reconnect = true @@ -364,7 +364,7 @@ module ActiveRecord # Is there an open connection that is being used for the current thread? # - # This method only works for connections that have been abtained through + # This method only works for connections that have been obtained through # #connection or #with_connection methods, connections obtained through # #checkout will not be detected by #active_connection? def active_connection? @@ -427,7 +427,7 @@ module ActiveRecord # The pool first tries to gain ownership of all connections, if unable to # do so within a timeout interval (default duration is # +spec.config[:checkout_timeout] * 2+ seconds), the pool is forcefully - # disconneted wihout any regard for other connection owning threads. + # disconnected without any regard for other connection owning threads. def disconnect! disconnect(false) end @@ -587,7 +587,7 @@ module ActiveRecord end #-- - # From the discussion on Github: + # From the discussion on GitHub: # https://github.com/rails/rails/pull/14938#commitcomment-6601951 # This hook-in method allows for easier monkey-patching fixes needed by # JRuby users that use Fibers. @@ -824,11 +824,11 @@ module ActiveRecord # These caches are keyed by klass.name, NOT klass. Keying them by klass # alone would lead to memory leaks in development mode as all previous # instances of the class would stay in memory. - @owner_to_pool = ThreadSafe::Cache.new(:initial_capacity => 2) do |h,k| - h[k] = ThreadSafe::Cache.new(:initial_capacity => 2) + @owner_to_pool = Concurrent::Map.new(:initial_capacity => 2) do |h,k| + h[k] = Concurrent::Map.new(:initial_capacity => 2) end - @class_to_pool = ThreadSafe::Cache.new(:initial_capacity => 2) do |h,k| - h[k] = ThreadSafe::Cache.new + @class_to_pool = Concurrent::Map.new(:initial_capacity => 2) do |h,k| + h[k] = Concurrent::Map.new end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index 1e13b24867..107806cd93 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -137,7 +137,7 @@ module ActiveRecord # # In order to get around this problem, #transaction will emulate the effect # of nested transactions, by using savepoints: - # http://dev.mysql.com/doc/refman/5.6/en/savepoint.html + # http://dev.mysql.com/doc/refman/5.7/en/savepoint.html # Savepoints are supported by MySQL and PostgreSQL. SQLite3 version >= '3.6.8' # supports savepoints. # @@ -190,7 +190,7 @@ module ActiveRecord # semantics of these different levels: # # * http://www.postgresql.org/docs/current/static/transaction-iso.html - # * https://dev.mysql.com/doc/refman/5.6/en/set-transaction.html + # * https://dev.mysql.com/doc/refman/5.7/en/set-transaction.html # # An <tt>ActiveRecord::TransactionIsolationError</tt> will be raised if: # diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb index 18d943f452..0ba4d94e3c 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb @@ -14,8 +14,10 @@ module ActiveRecord send m, o end - delegate :quote_column_name, :quote_table_name, :quote_default_expression, :type_to_sql, to: :@conn - private :quote_column_name, :quote_table_name, :quote_default_expression, :type_to_sql + delegate :quote_column_name, :quote_table_name, :quote_default_expression, :type_to_sql, + :options_include_default?, :supports_indexes_in_create?, :supports_foreign_keys?, :foreign_key_options, to: :@conn + private :quote_column_name, :quote_table_name, :quote_default_expression, :type_to_sql, + :options_include_default?, :supports_indexes_in_create?, :supports_foreign_keys?, :foreign_key_options private @@ -38,17 +40,32 @@ module ActiveRecord end def visit_TableDefinition(o) - create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE " - create_sql << "#{quote_table_name(o.name)} " - create_sql << "(#{o.columns.map { |c| accept c }.join(', ')}) " unless o.as + create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE #{quote_table_name(o.name)} " + + statements = o.columns.map { |c| accept c } + statements << accept(o.primary_keys) if o.primary_keys + + if supports_indexes_in_create? + statements.concat(o.indexes.map { |column_name, options| index_in_create(o.name, column_name, options) }) + end + + if supports_foreign_keys? + statements.concat(o.foreign_keys.map { |to_table, options| foreign_key_in_create(o.name, to_table, options) }) + end + + create_sql << "(#{statements.join(', ')}) " if statements.present? create_sql << "#{o.options}" create_sql << " AS #{@conn.to_sql(o.as)}" if o.as create_sql end - def visit_AddForeignKey(o) + def visit_PrimaryKeyDefinition(o) + "PRIMARY KEY (#{o.name.join(', ')})" + end + + def visit_ForeignKeyDefinition(o) sql = <<-SQL.strip_heredoc - ADD CONSTRAINT #{quote_column_name(o.name)} + CONSTRAINT #{quote_column_name(o.name)} FOREIGN KEY (#{quote_column_name(o.column)}) REFERENCES #{quote_table_name(o.to_table)} (#{quote_column_name(o.primary_key)}) SQL @@ -57,6 +74,10 @@ module ActiveRecord sql end + def visit_AddForeignKey(o) + "ADD #{accept(o)}" + end + def visit_DropForeignKey(name) "DROP CONSTRAINT #{quote_column_name(name)}" end @@ -89,8 +110,9 @@ module ActiveRecord sql end - def options_include_default?(options) - options.include?(:default) && !(options[:null] == false && options[:default].nil?) + def foreign_key_in_create(from_table, to_table, options) + options = foreign_key_options(from_table, to_table, options) + accept ForeignKeyDefinition.new(from_table, to_table, options) end def action_sql(action, dependency) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index 3115e03ea2..10329de5f4 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -23,6 +23,9 @@ module ActiveRecord class ChangeColumnDefinition < Struct.new(:column, :name) #:nodoc: end + class PrimaryKeyDefinition < Struct.new(:name) # :nodoc: + end + class ForeignKeyDefinition < Struct.new(:from_table, :to_table, :options) #:nodoc: def name options[:name] @@ -207,6 +210,7 @@ module ActiveRecord @columns_hash = {} @indexes = {} @foreign_keys = {} + @primary_keys = nil @native = types @temporary = temporary @options = options @@ -214,6 +218,11 @@ module ActiveRecord @name = name end + def primary_keys(name = nil) # :nodoc: + @primary_keys = PrimaryKeyDefinition.new(name) if name + @primary_keys + end + # Returns an array of ColumnDefinition objects for the columns of the table. def columns; @columns_hash.values; end @@ -236,8 +245,8 @@ module ActiveRecord # # Available options are (none of these exists by default): # * <tt>:limit</tt> - - # Requests a maximum column length. This is number of characters for <tt>:string</tt> and - # <tt>:text</tt> columns and number of bytes for <tt>:binary</tt> and <tt>:integer</tt> columns. + # Requests a maximum column length. This is number of characters for a <tt>:string</tt> column + # and number of bytes for <tt>:text</tt>, <tt>:binary</tt> and <tt>:integer</tt> columns. # * <tt>:default</tt> - # The column's default value. Use nil for NULL. # * <tt>:null</tt> - diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 08803303cf..40175c8c8c 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -36,6 +36,19 @@ module ActiveRecord tables.include?(table_name.to_s) end + # Returns an array of view names defined in the database. + def views + raise NotImplementedError, "#views is not implemented" + end + + # Checks to see if the view +view_name+ exists on the database. + # + # view_exists?(:ebooks) + # + def view_exists?(view_name) + views.include?(view_name.to_s) + end + # Returns an array of indexes for the given table. # def indexes(table_name, name = nil) end @@ -93,6 +106,12 @@ module ActiveRecord (!options.key?(:null) || c.null == options[:null]) } end + # Returns just a table's primary key + def primary_key(table_name) + pks = primary_keys(table_name) + pks.first if pks.one? + end + # Creates a new table with the name +table_name+. +table_name+ may either # be a String or a Symbol. # @@ -158,7 +177,7 @@ module ActiveRecord # generates: # # CREATE TABLE suppliers ( - # id int(11) DEFAULT NULL auto_increment PRIMARY KEY + # id int auto_increment PRIMARY KEY # ) ENGINE=InnoDB DEFAULT CHARSET=utf8 # # ====== Rename the primary key column @@ -170,7 +189,7 @@ module ActiveRecord # generates: # # CREATE TABLE objects ( - # guid int(11) DEFAULT NULL auto_increment PRIMARY KEY, + # guid int auto_increment PRIMARY KEY, # name varchar(80) # ) # @@ -220,7 +239,11 @@ module ActiveRecord Base.get_primary_key table_name.to_s.singularize end - td.primary_key pk, options.fetch(:id, :primary_key), options + if pk.is_a?(Array) + td.primary_keys pk + else + td.primary_key pk, options.fetch(:id, :primary_key), options + end end yield td if block_given? @@ -237,10 +260,6 @@ module ActiveRecord end end - td.foreign_keys.each_pair do |other_table_name, foreign_key_options| - add_foreign_key(table_name, other_table_name, foreign_key_options) - end - result end @@ -320,7 +339,7 @@ module ActiveRecord # [<tt>:bulk</tt>] # Set this to true to make this a bulk alter query, such as # - # ALTER TABLE `users` ADD COLUMN age INT(11), ADD COLUMN birthdate DATETIME ... + # ALTER TABLE `users` ADD COLUMN age INT, ADD COLUMN birthdate DATETIME ... # # Defaults to false. # @@ -540,6 +559,8 @@ module ActiveRecord # # CREATE INDEX by_name ON accounts(name(10)) # + # ====== Creating an index with specific key lengths for multiple keys + # # add_index(:accounts, [:name, :surname], name: 'by_name_surname', length: {name: 10, surname: 15}) # # generates: @@ -769,15 +790,7 @@ module ActiveRecord def add_foreign_key(from_table, to_table, options = {}) return unless supports_foreign_keys? - options[:column] ||= foreign_key_column_for(to_table) - - options = { - column: options[:column], - primary_key: options[:primary_key], - name: foreign_key_name(from_table, options), - on_delete: options[:on_delete], - on_update: options[:on_update] - } + options = foreign_key_options(from_table, to_table, options) at = create_alter_table from_table at.add_foreign_key to_table, options @@ -845,6 +858,13 @@ module ActiveRecord "#{name.singularize}_id" end + def foreign_key_options(from_table, to_table, options) # :nodoc: + options = options.dup + options[:column] ||= foreign_key_column_for(to_table) + options[:name] ||= foreign_key_name(from_table, options) + options + end + def dump_schema_information #:nodoc: sm_table = ActiveRecord::Migrator.schema_migrations_table_name @@ -986,6 +1006,10 @@ module ActiveRecord [index_name, index_type, index_columns, index_options, algorithm, using] end + def options_include_default?(options) + options.include?(:default) && !(options[:null] == false && options[:default].nil?) + end + protected def add_index_sort_order(option_strings, column_names, options = {}) if options.is_a?(Hash) && order = options[:order] @@ -1012,10 +1036,6 @@ module ActiveRecord column_names.map {|name| quote_column_name(name) + option_strings[name]} end - def options_include_default?(options) - options.include?(:default) && !(options[:null] == false && options[:default].nil?) - end - def index_name_for_remove(table_name, options = {}) index_name = index_name(table_name, options) 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 7b47539596..feb41e96f0 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -14,10 +14,26 @@ module ActiveRecord def json(*args, **options) args.each { |name| column(name, :json, options) } end + + def unsigned_integer(*args, **options) + args.each { |name| column(name, :unsigned_integer, options) } + end + + def unsigned_bigint(*args, **options) + args.each { |name| column(name, :unsigned_bigint, options) } + end + + def unsigned_float(*args, **options) + args.each { |name| column(name, :unsigned_float, options) } + end + + def unsigned_decimal(*args, **options) + args.each { |name| column(name, :unsigned_decimal, options) } + end end class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition - attr_accessor :charset + attr_accessor :charset, :unsigned end class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition @@ -29,7 +45,11 @@ module ActiveRecord when :primary_key column.type = :integer column.auto_increment = true + when /\Aunsigned_(?<type>.+)\z/ + column.type = $~[:type].to_sym + column.unsigned = true end + column.unsigned ||= options[:unsigned] column.charset = options[:charset] column end @@ -52,17 +72,9 @@ module ActiveRecord "DROP FOREIGN KEY #{name}" end - def visit_TableDefinition(o) - name = o.name - create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE #{quote_table_name(name)} " - - statements = o.columns.map { |c| accept c } - statements.concat(o.indexes.map { |column_name, options| index_in_create(name, column_name, options) }) - - create_sql << "(#{statements.join(', ')}) " if statements.present? - create_sql << "#{o.options}" - create_sql << " AS #{@conn.to_sql(o.as)}" if o.as - create_sql + def visit_ColumnDefinition(o) + o.sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale, o.unsigned) + super end def visit_AddColumnDefinition(o) @@ -117,6 +129,7 @@ module ActiveRecord spec = {} if column.auto_increment? spec[:id] = ':bigint' if column.bigint? + spec[:unsigned] = 'true' if column.unsigned? return if spec.empty? else spec[:id] = column.type.inspect @@ -125,6 +138,16 @@ module ActiveRecord spec end + def prepare_column_options(column) + spec = super + spec[:unsigned] = 'true' if column.unsigned? + spec + end + + def migration_keys + super + [:unsigned] + end + private def schema_limit(column) @@ -171,6 +194,10 @@ module ActiveRecord sql_type =~ /blob/i || type == :text end + def unsigned? + /unsigned/ === sql_type + end + def case_sensitive? collation && !collation.match(/_ci$/) end @@ -246,7 +273,7 @@ module ActiveRecord QUOTED_TRUE, QUOTED_FALSE = '1', '0' NATIVE_DATABASE_TYPES = { - primary_key: "int(11) auto_increment PRIMARY KEY", + primary_key: "int auto_increment PRIMARY KEY", string: { name: "varchar", limit: 255 }, text: { name: "text" }, integer: { name: "int", limit: 4 }, @@ -316,6 +343,10 @@ module ActiveRecord version >= '5.0.0' end + def supports_explain? + true + end + def supports_indexes_in_create? true end @@ -417,6 +448,80 @@ module ActiveRecord # DATABASE STATEMENTS ====================================== #++ + def explain(arel, binds = []) + sql = "EXPLAIN #{to_sql(arel, binds)}" + start = Time.now + result = exec_query(sql, 'EXPLAIN', binds) + elapsed = Time.now - start + + ExplainPrettyPrinter.new.pp(result, elapsed) + end + + class ExplainPrettyPrinter # :nodoc: + # Pretty prints the result of a EXPLAIN in a way that resembles the output of the + # MySQL shell: + # + # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+ + # | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | + # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+ + # | 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | | + # | 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where | + # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+ + # 2 rows in set (0.00 sec) + # + # This is an exercise in Ruby hyperrealism :). + def pp(result, elapsed) + widths = compute_column_widths(result) + separator = build_separator(widths) + + pp = [] + + pp << separator + pp << build_cells(result.columns, widths) + pp << separator + + result.rows.each do |row| + pp << build_cells(row, widths) + end + + pp << separator + pp << build_footer(result.rows.length, elapsed) + + pp.join("\n") + "\n" + end + + private + + def compute_column_widths(result) + [].tap do |widths| + result.columns.each_with_index do |column, i| + cells_in_column = [column] + result.rows.map {|r| r[i].nil? ? 'NULL' : r[i].to_s} + widths << cells_in_column.map(&:length).max + end + end + end + + def build_separator(widths) + padding = 1 + '+' + widths.map {|w| '-' * (w + (padding*2))}.join('+') + '+' + end + + def build_cells(items, widths) + cells = [] + items.each_with_index do |item, i| + item = 'NULL' if item.nil? + justifier = item.is_a?(Numeric) ? 'rjust' : 'ljust' + cells << item.to_s.send(justifier, widths[i]) + end + '| ' + cells.join(' | ') + ' |' + end + + def build_footer(nrows, elapsed) + rows_label = nrows == 1 ? 'row' : 'rows' + "#{nrows} #{rows_label} in set (%.2f sec)" % elapsed + end + end + def clear_cache! super reload_type_map @@ -520,33 +625,40 @@ module ActiveRecord show_variable 'collation_database' end - def tables(name = nil, database = nil, like = nil) #:nodoc: - sql = "SHOW TABLES " - sql << "IN #{quote_table_name(database)} " if database - sql << "LIKE #{quote(like)}" if like - - execute_and_free(sql, 'SCHEMA') do |result| - result.collect(&:first) - end + def tables(name = nil) # :nodoc: + select_values("SHOW FULL TABLES", 'SCHEMA') end def truncate(table_name, name = nil) execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name end - def table_exists?(name) - return false unless name.present? - return true if tables(nil, nil, name).any? + def table_exists?(table_name) + return false unless table_name.present? - name = name.to_s - schema, table = name.split('.', 2) + schema, name = table_name.to_s.split('.', 2) + schema, name = @config[:database], schema unless name # A table was provided without a schema - unless table # A table was provided without a schema - table = schema - schema = nil - end + sql = "SELECT table_name FROM information_schema.tables " + sql << "WHERE table_schema = #{quote(schema)} AND table_name = #{quote(name)}" + + select_values(sql, 'SCHEMA').any? + end - tables(nil, schema, table).any? + def views # :nodoc: + select_values("SHOW FULL TABLES WHERE table_type = 'VIEW'", 'SCHEMA') + end + + def view_exists?(view_name) # :nodoc: + return false unless view_name.present? + + schema, name = view_name.to_s.split('.', 2) + schema, name = @config[:database], schema unless name # A view was provided without a schema + + sql = "SELECT table_name FROM information_schema.tables WHERE table_type = 'VIEW'" + sql << " AND table_schema = #{quote(schema)} AND table_name = #{quote(name)}" + + select_values(sql, 'SCHEMA').any? end # Returns an array of indexes for the given table. @@ -685,7 +797,7 @@ module ActiveRecord AND fk.table_name = '#{table_name}' SQL - create_table_info = select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"] + create_table_info = create_table_info(table_name) fk_info.map do |row| options = { @@ -702,7 +814,7 @@ module ActiveRecord end def table_options(table_name) - create_table_info = select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"] + create_table_info = create_table_info(table_name) # strip create_definitions and partition_options raw_table_options = create_table_info.sub(/\A.*\n\) /m, '').sub(/\n\/\*!.*\*\/\n\z/m, '').strip @@ -712,8 +824,8 @@ module ActiveRecord end # Maps logical Rails types to MySQL-specific data types. - def type_to_sql(type, limit = nil, precision = nil, scale = nil) - case type.to_s + def type_to_sql(type, limit = nil, precision = nil, scale = nil, unsigned = nil) + sql = case type.to_s when 'binary' binary_to_sql(limit) when 'integer' @@ -721,8 +833,11 @@ module ActiveRecord when 'text' text_to_sql(limit) else - super + super(type, limit, precision, scale) end + + sql << ' unsigned' if unsigned && type != :primary_key + sql end # SHOW VARIABLES LIKE 'name' @@ -735,21 +850,25 @@ module ActiveRecord # Returns a table's primary key and belonging sequence. def pk_and_sequence_for(table) - execute_and_free("SHOW CREATE TABLE #{quote_table_name(table)}", 'SCHEMA') do |result| - create_table = each_hash(result).first[:"Create Table"] - if create_table.to_s =~ /PRIMARY KEY\s+(?:USING\s+\w+\s+)?\((.+)\)/ - keys = $1.split(",").map { |key| key.delete('`"') } - keys.length == 1 ? [keys.first, nil] : nil - else - nil - end + if pk = primary_key(table) + [ pk, nil ] end end - # Returns just a table's primary key - def primary_key(table) - pk_and_sequence = pk_and_sequence_for(table) - pk_and_sequence && pk_and_sequence.first + def primary_keys(table_name) # :nodoc: + raise ArgumentError unless table_name.present? + + schema, name = table_name.to_s.split('.', 2) + schema, name = @config[:database], schema unless name # A table was provided without a schema + + select_values(<<-SQL.strip_heredoc, 'SCHEMA') + SELECT column_name + FROM information_schema.key_column_usage + WHERE constraint_name = 'PRIMARY' + AND table_schema = #{quote(schema)} + AND table_name = #{quote(name)} + ORDER BY ordinal_position + SQL end def case_sensitive_modifier(node, table_attribute) @@ -807,7 +926,6 @@ module ActiveRecord register_integer_type m, %r(^tinyint)i, limit: 1 m.alias_type %r(tinyint\(1\))i, 'boolean' if emulate_booleans - m.alias_type %r(set)i, 'varchar' m.alias_type %r(year)i, 'integer' m.alias_type %r(bit)i, 'binary' @@ -816,6 +934,12 @@ module ActiveRecord .split(',').map{|enum| enum.strip.length - 2}.max MysqlString.new(limit: limit) end + + m.register_type(%r(^set)i) do |sql_type| + limit = sql_type[/^set\((.+)\)/i, 1] + .split(',').map{|set| set.strip.length - 1}.sum - 1 + MysqlString.new(limit: limit) + end end def register_integer_type(mapping, key, options) # :nodoc: @@ -980,14 +1104,14 @@ module ActiveRecord defaults = [':default', :default].to_set # Make MySQL reject illegal values rather than truncating or blanking them, see - # http://dev.mysql.com/doc/refman/5.6/en/sql-mode.html#sqlmode_strict_all_tables + # http://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sqlmode_strict_all_tables # If the user has provided another value for sql_mode, don't replace it. unless variables.has_key?('sql_mode') || defaults.include?(@config[:strict]) variables['sql_mode'] = strict_mode? ? 'STRICT_ALL_TABLES' : '' end # NAMES does not have an equals sign, see - # http://dev.mysql.com/doc/refman/5.6/en/set-statement.html#id944430 + # http://dev.mysql.com/doc/refman/5.7/en/set-statement.html#id944430 # (trailing comma because variable_assignments will always have content) if @config[:encoding] encoding = "NAMES #{@config[:encoding]}" @@ -1018,6 +1142,11 @@ module ActiveRecord end end + def create_table_info(table_name) # :nodoc: + @create_table_info_cache = {} + @create_table_info_cache[table_name] ||= select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"] + end + def create_table_definition(name, temporary = false, options = nil, as = nil) # :nodoc: TableDefinition.new(native_database_types, name, temporary, options, as) end @@ -1027,7 +1156,7 @@ module ActiveRecord when 0..0xfff; "varbinary(#{limit})" when nil; "blob" when 0x1000..0xffffffff; "blob(#{limit})" - else raise(ActiveRecordError, "No binary type has character length #{limit}") + else raise(ActiveRecordError, "No binary type has byte length #{limit}") end end @@ -1036,8 +1165,9 @@ module ActiveRecord when 1; 'tinyint' when 2; 'smallint' when 3; 'mediumint' - when nil, 4, 11; 'int(11)' # compatibility with MySQL default + when nil, 4; 'int' when 5..8; 'bigint' + when 11; 'int(11)' # backward compatibility with Rails 2.0 else raise(ActiveRecordError, "No integer type has byte size #{limit}") end end @@ -1048,7 +1178,7 @@ module ActiveRecord when nil, 0x100..0xffff; 'text' when 0x10000..0xffffff; 'mediumtext' when 0x1000000..0xffffffff; 'longtext' - else raise(ActiveRecordError, "No text type has character length #{limit}") + else raise(ActiveRecordError, "No text type has byte length #{limit}") end end @@ -1084,6 +1214,8 @@ module ActiveRecord ActiveRecord::Type.register(:json, MysqlJson, adapter: :mysql2) ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql) ActiveRecord::Type.register(:string, MysqlString, adapter: :mysql2) + ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql) + ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2) end end end diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb index 4b95b0681d..8222811e0c 100644 --- a/activerecord/lib/active_record/connection_adapters/column.rb +++ b/activerecord/lib/active_record/connection_adapters/column.rb @@ -18,7 +18,7 @@ module ActiveRecord # Instantiates a new column in the table. # - # +name+ is the column's name, such as <tt>supplier_id</tt> in <tt>supplier_id int(11)</tt>. + # +name+ is the column's name, such as <tt>supplier_id</tt> in <tt>supplier_id int</tt>. # +default+ is the type-casted default value, such as +new+ in <tt>sales_stage varchar(20) default 'new'</tt>. # +sql_type_metadata+ is various information about the type of the column # +null+ determines if this column allows +NULL+ values. diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 734b384e80..4461722bb4 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -37,10 +37,6 @@ module ActiveRecord configure_connection end - def supports_explain? - true - end - def supports_json? version >= '5.7.8' end @@ -99,80 +95,6 @@ module ActiveRecord # DATABASE STATEMENTS ====================================== #++ - def explain(arel, binds = []) - sql = "EXPLAIN #{to_sql(arel, binds.dup)}" - start = Time.now - result = exec_query(sql, 'EXPLAIN', binds) - elapsed = Time.now - start - - ExplainPrettyPrinter.new.pp(result, elapsed) - end - - class ExplainPrettyPrinter # :nodoc: - # Pretty prints the result of a EXPLAIN in a way that resembles the output of the - # MySQL shell: - # - # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+ - # | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | - # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+ - # | 1 | SIMPLE | users | const | PRIMARY | PRIMARY | 4 | const | 1 | | - # | 1 | SIMPLE | posts | ALL | NULL | NULL | NULL | NULL | 1 | Using where | - # +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------------+ - # 2 rows in set (0.00 sec) - # - # This is an exercise in Ruby hyperrealism :). - def pp(result, elapsed) - widths = compute_column_widths(result) - separator = build_separator(widths) - - pp = [] - - pp << separator - pp << build_cells(result.columns, widths) - pp << separator - - result.rows.each do |row| - pp << build_cells(row, widths) - end - - pp << separator - pp << build_footer(result.rows.length, elapsed) - - pp.join("\n") + "\n" - end - - private - - def compute_column_widths(result) - [].tap do |widths| - result.columns.each_with_index do |column, i| - cells_in_column = [column] + result.rows.map {|r| r[i].nil? ? 'NULL' : r[i].to_s} - widths << cells_in_column.map(&:length).max - end - end - end - - def build_separator(widths) - padding = 1 - '+' + widths.map {|w| '-' * (w + (padding*2))}.join('+') + '+' - end - - def build_cells(items, widths) - cells = [] - items.each_with_index do |item, i| - item = 'NULL' if item.nil? - justifier = item.is_a?(Numeric) ? 'rjust' : 'ljust' - cells << item.to_s.send(justifier, widths[i]) - end - '| ' + cells.join(' | ') + ' |' - end - - def build_footer(nrows, elapsed) - rows_label = nrows == 1 ? 'row' : 'rows' - "#{nrows} #{rows_label} in set (%.2f sec)" % elapsed - end - end - # FIXME: re-enable the following once a "better" query_cache solution is in core # # The overrides below perform much better than the originals in AbstractAdapter diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 0738c59ddf..b3894481cc 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -58,9 +58,9 @@ module ActiveRecord # * <tt>:password</tt> - Defaults to nothing. # * <tt>:database</tt> - The name of the database. No default, must be provided. # * <tt>:encoding</tt> - (Optional) Sets the client encoding by executing "SET NAMES <encoding>" after connection. - # * <tt>:reconnect</tt> - Defaults to false (See MySQL documentation: http://dev.mysql.com/doc/refman/5.6/en/auto-reconnect.html). - # * <tt>:strict</tt> - Defaults to true. Enable STRICT_ALL_TABLES. (See MySQL documentation: http://dev.mysql.com/doc/refman/5.6/en/sql-mode.html) - # * <tt>:variables</tt> - (Optional) A hash session variables to send as <tt>SET @@SESSION.key = value</tt> on each database connection. Use the value +:default+ to set a variable to its DEFAULT value. (See MySQL documentation: http://dev.mysql.com/doc/refman/5.6/en/set-statement.html). + # * <tt>:reconnect</tt> - Defaults to false (See MySQL documentation: http://dev.mysql.com/doc/refman/5.7/en/auto-reconnect.html). + # * <tt>:strict</tt> - Defaults to true. Enable STRICT_ALL_TABLES. (See MySQL documentation: http://dev.mysql.com/doc/refman/5.7/en/sql-mode.html) + # * <tt>:variables</tt> - (Optional) A hash session variables to send as <tt>SET @@SESSION.key = value</tt> on each database connection. Use the value +:default+ to set a variable to its DEFAULT value. (See MySQL documentation: http://dev.mysql.com/doc/refman/5.7/en/set-statement.html). # * <tt>:sslca</tt> - Necessary to use MySQL with an SSL connection. # * <tt>:sslkey</tt> - Necessary to use MySQL with an SSL connection. # * <tt>:sslcert</tt> - Necessary to use MySQL with an SSL connection. @@ -80,8 +80,7 @@ module ActiveRecord def initialize(connection, logger, connection_options, config) super - @statements = StatementPool.new(@connection, - self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 })) + @statements = StatementPool.new(self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 })) @client_encoding = nil connect end @@ -162,6 +161,14 @@ module ActiveRecord # DATABASE STATEMENTS ====================================== #++ + def select_all(arel, name = nil, binds = []) + if ExplainRegistry.collect? && prepared_statements + unprepared_statement { super } + else + super + end + end + def select_rows(sql, name = nil, binds = []) @connection.query_with_result = true rows = exec_query(sql, name, binds).rows 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 69aa02ccf4..66d311dd48 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -90,6 +90,30 @@ module ActiveRecord SQL end + def views # :nodoc: + select_values(<<-SQL, 'SCHEMA') + SELECT c.relname + FROM pg_class c + LEFT JOIN pg_namespace n ON n.oid = c.relnamespace + WHERE c.relkind IN ('v','m') -- (v)iew, (m)aterialized view + AND n.nspname = ANY (current_schemas(false)) + SQL + end + + def view_exists?(view_name) # :nodoc: + name = Utils.extract_schema_qualified_name(view_name.to_s) + return false unless name.identifier + + select_values(<<-SQL, 'SCHEMA').any? + SELECT c.relname + FROM pg_class c + LEFT JOIN pg_namespace n ON n.oid = c.relnamespace + WHERE c.relkind IN ('v','m') -- (v)iew, (m)aterialized view + AND c.relname = '#{name.identifier}' + AND n.nspname = #{name.schema ? "'#{name.schema}'" : 'ANY (current_schemas(false))'} + SQL + end + def drop_table(table_name, options = {}) # :nodoc: execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}" end @@ -353,17 +377,19 @@ module ActiveRecord nil end - # Returns just a table's primary key - def primary_key(table) - pks = query(<<-end_sql, 'SCHEMA') - SELECT attr.attname - FROM pg_attribute attr - INNER JOIN pg_constraint cons ON attr.attrelid = cons.conrelid AND attr.attnum = any(cons.conkey) - WHERE cons.contype = 'p' - AND cons.conrelid = '#{quote_table_name(table)}'::regclass - end_sql - return nil unless pks.count == 1 - pks[0][0] + def primary_keys(table_name) # :nodoc: + select_values(<<-SQL.strip_heredoc, 'SCHEMA') + WITH pk_constraint AS ( + SELECT conrelid, unnest(conkey) AS connum FROM pg_constraint + WHERE contype = 'p' + AND conrelid = '#{quote_table_name(table_name)}'::regclass + ), cons AS ( + SELECT conrelid, connum, row_number() OVER() AS rownum FROM pk_constraint + ) + SELECT attr.attname FROM pg_attribute attr + INNER JOIN cons ON attr.attrelid = cons.conrelid AND attr.attnum = cons.connum + ORDER BY cons.rownum + SQL end # Renames a table. diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 27291bd2ea..9de79a614c 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -211,7 +211,8 @@ module ActiveRecord class StatementPool < ConnectionAdapters::StatementPool def initialize(connection, max) - super + super(max) + @connection = connection @counter = 0 end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index 24fc67938d..e2f52707e8 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -81,8 +81,7 @@ module ActiveRecord super(connection, logger) @active = nil - @statements = StatementPool.new(@connection, - self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 })) + @statements = StatementPool.new(self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 })) @config = config @visitor = Arel::Visitors::SQLite.new self @@ -325,6 +324,19 @@ module ActiveRecord table_name && tables(nil, table_name).any? end + def views # :nodoc: + select_values("SELECT name FROM sqlite_master WHERE type = 'view' AND name <> 'sqlite_sequence'", 'SCHEMA') + end + + def view_exists?(view_name) # :nodoc: + return false unless view_name.present? + + sql = "SELECT name FROM sqlite_master WHERE type = 'view' AND name <> 'sqlite_sequence'" + sql << " AND name = #{quote(view_name)}" + + select_values(sql, 'SCHEMA').any? + end + # Returns an array of +Column+ objects for the table specified by +table_name+. def columns(table_name) #:nodoc: table_structure(table_name).map do |field| @@ -369,10 +381,9 @@ module ActiveRecord end end - def primary_key(table_name) #:nodoc: + def primary_keys(table_name) # :nodoc: pks = table_structure(table_name).select { |f| f['pk'] > 0 } - return nil unless pks.count == 1 - pks[0]['name'] + pks.sort_by { |f| f['pk'] }.map { |f| f['name'] } end def remove_index(table_name, options = {}) #:nodoc: diff --git a/activerecord/lib/active_record/connection_adapters/statement_pool.rb b/activerecord/lib/active_record/connection_adapters/statement_pool.rb index 82e9ef3d3d..57463dd749 100644 --- a/activerecord/lib/active_record/connection_adapters/statement_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/statement_pool.rb @@ -3,10 +3,9 @@ module ActiveRecord class StatementPool include Enumerable - def initialize(connection, max = 1000) + def initialize(max = 1000) @cache = Hash.new { |h,pid| h[pid] = {} } - @connection = connection - @max = max + @max = max end def each(&block) diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb index 4902fcb1a2..a79bf0366b 100644 --- a/activerecord/lib/active_record/enum.rb +++ b/activerecord/lib/active_record/enum.rb @@ -204,30 +204,22 @@ module ActiveRecord def detect_enum_conflict!(enum_name, method_name, klass_method = false) if klass_method && dangerous_class_method?(method_name) - raise ArgumentError, ENUM_CONFLICT_MESSAGE % { - enum: enum_name, - klass: self.name, - type: 'class', - method: method_name, - source: 'Active Record' - } + raise_conflict_error(enum_name, method_name, type: 'class') elsif !klass_method && dangerous_attribute_method?(method_name) - raise ArgumentError, ENUM_CONFLICT_MESSAGE % { - enum: enum_name, - klass: self.name, - type: 'instance', - method: method_name, - source: 'Active Record' - } + raise_conflict_error(enum_name, method_name) elsif !klass_method && method_defined_within?(method_name, _enum_methods_module, Module) - raise ArgumentError, ENUM_CONFLICT_MESSAGE % { - enum: enum_name, - klass: self.name, - type: 'instance', - method: method_name, - source: 'another enum' - } + raise_conflict_error(enum_name, method_name, source: 'another enum') end end + + def raise_conflict_error(enum_name, method_name, type: 'instance', source: 'Active Record') + raise ArgumentError, ENUM_CONFLICT_MESSAGE % { + enum: enum_name, + klass: self.name, + type: type, + method: method_name, + source: source + } + end end end diff --git a/activerecord/lib/active_record/integration.rb b/activerecord/lib/active_record/integration.rb index 15b2f65dcb..370c1562c9 100644 --- a/activerecord/lib/active_record/integration.rb +++ b/activerecord/lib/active_record/integration.rb @@ -84,7 +84,7 @@ module ActiveRecord # Values longer than 20 characters will be truncated. The value # is truncated word by word. # - # user = User.find_by(name: 'David HeinemeierHansson') + # user = User.find_by(name: 'David Heinemeier Hansson') # user.id # => 125 # user_path(user) # => "/users/125-david" # diff --git a/activerecord/lib/active_record/locking/pessimistic.rb b/activerecord/lib/active_record/locking/pessimistic.rb index 3d95c54ef3..8ecdf76b72 100644 --- a/activerecord/lib/active_record/locking/pessimistic.rb +++ b/activerecord/lib/active_record/locking/pessimistic.rb @@ -51,7 +51,7 @@ module ActiveRecord # end # # Database-specific information on row locking: - # MySQL: http://dev.mysql.com/doc/refman/5.6/en/innodb-locking-reads.html + # MySQL: http://dev.mysql.com/doc/refman/5.7/en/innodb-locking-reads.html # PostgreSQL: http://www.postgresql.org/docs/current/interactive/sql-select.html#SQL-FOR-UPDATE-SHARE module Pessimistic # Obtain a row lock on this record. Reloads the record to obtain the requested diff --git a/activerecord/lib/active_record/null_relation.rb b/activerecord/lib/active_record/null_relation.rb index 74894d0c37..0b500346bc 100644 --- a/activerecord/lib/active_record/null_relation.rb +++ b/activerecord/lib/active_record/null_relation.rb @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - module ActiveRecord module NullRelation # :nodoc: def exec_queries diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 2b648978b3..4e0fc407bc 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -66,7 +66,6 @@ module ActiveRecord # # Account.reflections # => {"balance" => AggregateReflection} # - # @api public def reflections @__reflections ||= begin ref = {} @@ -96,7 +95,6 @@ module ActiveRecord # Account.reflect_on_all_associations # returns an array of all associations # Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations # - # @api public def reflect_on_all_associations(macro = nil) association_reflections = reflections.values association_reflections.select! { |reflection| reflection.macro == macro } if macro @@ -108,24 +106,20 @@ module ActiveRecord # Account.reflect_on_association(:owner) # returns the owner AssociationReflection # Invoice.reflect_on_association(:line_items).macro # returns :has_many # - # @api public def reflect_on_association(association) reflections[association.to_s] end - # @api private def _reflect_on_association(association) #:nodoc: _reflections[association.to_s] end # Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled. - # - # @api public def reflect_on_all_autosave_associations reflections.values.select { |reflection| reflection.options[:autosave] } end - def clear_reflections_cache #:nodoc: + def clear_reflections_cache # :nodoc: @__reflections = nil end end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index bf08cdbbf3..e596df8742 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- require "arel/collectors/bind" module ActiveRecord diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index c5910fa1ad..f1f827f163 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -89,7 +89,7 @@ HEADER end def tables(stream) - sorted_tables = @connection.tables.sort + sorted_tables = @connection.tables.sort - @connection.views sorted_tables.each do |table_name| table(table_name, stream) unless ignored?(table_name) @@ -112,20 +112,27 @@ HEADER tbl = StringIO.new # first dump primary key column - pk = @connection.primary_key(table) + if @connection.respond_to?(:primary_keys) + pk = @connection.primary_keys(table) + pk = pk.first unless pk.size > 1 + else + pk = @connection.primary_key(table) + end tbl.print " create_table #{remove_prefix_and_suffix(table).inspect}" - pkcol = columns.detect { |c| c.name == pk } - if pkcol - if pk != 'id' - tbl.print %Q(, primary_key: "#{pk}") - end + + case pk + when String + tbl.print ", primary_key: #{pk.inspect}" unless pk == 'id' + pkcol = columns.detect { |c| c.name == pk } pkcolspec = @connection.column_spec_for_primary_key(pkcol) if pkcolspec pkcolspec.each do |key, value| tbl.print ", #{key}: #{value}" end end + when Array + tbl.print ", primary_key: #{pk.inspect}" else tbl.print ", id: false" end @@ -247,7 +254,7 @@ HEADER end def ignored?(table_name) - ['schema_migrations', ignore_tables].flatten.any? do |ignored| + [ActiveRecord::Base.schema_migrations_table_name, ignore_tables].flatten.any? do |ignored| ignored === remove_prefix_and_suffix(table_name) end end diff --git a/activerecord/lib/active_record/schema_migration.rb b/activerecord/lib/active_record/schema_migration.rb index cb47bf23f7..b384529e75 100644 --- a/activerecord/lib/active_record/schema_migration.rb +++ b/activerecord/lib/active_record/schema_migration.rb @@ -2,7 +2,11 @@ require 'active_record/scoping/default' require 'active_record/scoping/named' module ActiveRecord - class SchemaMigration < ActiveRecord::Base + # This class is used to create a table that keeps track of which migrations + # have been applied to a given database. When a migration is run, its schema + # number is inserted in to the `SchemaMigration.table_name` so it doesn't need + # to be executed the next time. + class SchemaMigration < ActiveRecord::Base # :nodoc: class << self def primary_key nil diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb index 673386f0d9..ba5dd0a0d9 100644 --- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb +++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb @@ -23,7 +23,7 @@ module ActiveRecord end rescue error_class => error if error.respond_to?(:errno) && error.errno == ACCESS_DENIED_ERROR - $stdout.print error.error + $stdout.print error.message establish_connection root_configuration_without_database connection.create_database configuration['database'], creation_options if configuration['username'] != 'root' diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index 887d7a5903..4a569fc242 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -167,7 +167,7 @@ module ActiveRecord # writing, the only database that we're aware of that supports true nested # transactions, is MS-SQL. Because of this, Active Record emulates nested # transactions by using savepoints on MySQL and PostgreSQL. See - # http://dev.mysql.com/doc/refman/5.6/en/savepoint.html + # http://dev.mysql.com/doc/refman/5.7/en/savepoint.html # for more information about savepoints. # # === Callbacks diff --git a/activerecord/lib/active_record/type/type_map.rb b/activerecord/lib/active_record/type/type_map.rb index 09f5ba6b74..8ce36cc9af 100644 --- a/activerecord/lib/active_record/type/type_map.rb +++ b/activerecord/lib/active_record/type/type_map.rb @@ -1,12 +1,12 @@ -require 'thread_safe' +require 'concurrent' module ActiveRecord module Type class TypeMap # :nodoc: def initialize @mapping = {} - @cache = ThreadSafe::Cache.new do |h, key| - h.fetch_or_store(key, ThreadSafe::Cache.new) + @cache = Concurrent::Map.new do |h, key| + h.fetch_or_store(key, Concurrent::Map.new) end end diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index 1712ff0ac6..9a111b37cd 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -63,7 +63,7 @@ module ActiveRecord end end - if current_adapter?(:MysqlAdapter) + if current_adapter?(:MysqlAdapter, :Mysql2Adapter) def test_charset assert_not_nil @connection.charset assert_not_equal 'character_set_database', @connection.charset diff --git a/activerecord/test/cases/adapters/mysql/active_schema_test.rb b/activerecord/test/cases/adapters/mysql/active_schema_test.rb index f0fd95ac16..0b5c9e1798 100644 --- a/activerecord/test/cases/adapters/mysql/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql/active_schema_test.rb @@ -100,17 +100,15 @@ class MysqlActiveSchemaTest < ActiveRecord::MysqlTestCase assert_equal "DROP TABLE `people`", drop_table(:people) end - if current_adapter?(:MysqlAdapter, :Mysql2Adapter) - def test_create_mysql_database_with_encoding - assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8`", create_database(:matt) - assert_equal "CREATE DATABASE `aimonetti` DEFAULT CHARACTER SET `latin1`", create_database(:aimonetti, {:charset => 'latin1'}) - assert_equal "CREATE DATABASE `matt_aimonetti` DEFAULT CHARACTER SET `big5` COLLATE `big5_chinese_ci`", create_database(:matt_aimonetti, {:charset => :big5, :collation => :big5_chinese_ci}) - end + def test_create_mysql_database_with_encoding + assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8`", create_database(:matt) + assert_equal "CREATE DATABASE `aimonetti` DEFAULT CHARACTER SET `latin1`", create_database(:aimonetti, {:charset => 'latin1'}) + assert_equal "CREATE DATABASE `matt_aimonetti` DEFAULT CHARACTER SET `big5` COLLATE `big5_chinese_ci`", create_database(:matt_aimonetti, {:charset => :big5, :collation => :big5_chinese_ci}) + end - def test_recreate_mysql_database_with_encoding - create_database(:luca, {:charset => 'latin1'}) - assert_equal "CREATE DATABASE `luca` DEFAULT CHARACTER SET `latin1`", recreate_database(:luca, {:charset => 'latin1'}) - end + def test_recreate_mysql_database_with_encoding + create_database(:luca, {:charset => 'latin1'}) + assert_equal "CREATE DATABASE `luca` DEFAULT CHARACTER SET `latin1`", recreate_database(:luca, {:charset => 'latin1'}) end def test_add_column diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb index ddbc007b87..8b62998964 100644 --- a/activerecord/test/cases/adapters/mysql/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql/connection_test.rb @@ -183,7 +183,7 @@ class MysqlConnectionTest < ActiveRecord::MysqlTestCase def with_example_table(&block) definition ||= <<-SQL - `id` int(11) auto_increment PRIMARY KEY, + `id` int auto_increment PRIMARY KEY, `data` varchar(255) SQL super(@connection, 'ex', definition, &block) diff --git a/activerecord/test/cases/adapters/mysql/explain_test.rb b/activerecord/test/cases/adapters/mysql/explain_test.rb new file mode 100644 index 0000000000..f75386abb9 --- /dev/null +++ b/activerecord/test/cases/adapters/mysql/explain_test.rb @@ -0,0 +1,27 @@ +require "cases/helper" +require 'models/developer' +require 'models/computer' + +module ActiveRecord + module ConnectionAdapters + class MysqlAdapter + class ExplainTest < ActiveRecord::TestCase + fixtures :developers + + def test_explain_for_one_query + explain = Developer.where(:id => 1).explain + assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain + assert_match %r(developers |.* const), explain + end + + def test_explain_with_eager_loading + explain = Developer.where(:id => 1).includes(:audit_logs).explain + assert_match %(EXPLAIN for: SELECT `developers`.* FROM `developers` WHERE `developers`.`id` = 1), explain + assert_match %r(developers |.* const), explain + assert_match %(EXPLAIN for: SELECT `audit_logs`.* FROM `audit_logs` WHERE `audit_logs`.`developer_id` = 1), explain + assert_match %r(audit_logs |.* ALL), explain + end + end + end + end +end diff --git a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb index b804cb45b9..8655fa26b6 100644 --- a/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb +++ b/activerecord/test/cases/adapters/mysql/mysql_adapter_test.rb @@ -1,4 +1,3 @@ - require "cases/helper" require 'support/ddl_helper' @@ -83,7 +82,7 @@ module ActiveRecord end def test_pk_and_sequence_for_with_non_standard_primary_key - with_example_table '`code` INT(11) auto_increment, PRIMARY KEY (`code`)' do + with_example_table '`code` INT auto_increment, PRIMARY KEY (`code`)' do pk, seq = @conn.pk_and_sequence_for('ex') assert_equal 'code', pk assert_equal @conn.default_sequence_name('ex', 'code'), seq @@ -91,7 +90,7 @@ module ActiveRecord end def test_pk_and_sequence_for_with_custom_index_type_pk - with_example_table '`id` INT(11) auto_increment, PRIMARY KEY USING BTREE (`id`)' do + with_example_table '`id` INT auto_increment, PRIMARY KEY USING BTREE (`id`)' do pk, seq = @conn.pk_and_sequence_for('ex') assert_equal 'id', pk assert_equal @conn.default_sequence_name('ex', 'id'), seq @@ -99,7 +98,7 @@ module ActiveRecord end def test_composite_primary_key - with_example_table '`id` INT(11), `number` INT(11), foo INT(11), PRIMARY KEY (`id`, `number`)' do + with_example_table '`id` INT, `number` INT, foo INT, PRIMARY KEY (`id`, `number`)' do assert_nil @conn.primary_key('ex') end end @@ -141,7 +140,7 @@ module ActiveRecord def with_example_table(definition = nil, &block) definition ||= <<-SQL - `id` int(11) auto_increment PRIMARY KEY, + `id` int auto_increment PRIMARY KEY, `number` integer, `data` varchar(255) SQL diff --git a/activerecord/test/cases/adapters/mysql/statement_pool_test.rb b/activerecord/test/cases/adapters/mysql/statement_pool_test.rb index 6be36566de..0d1f968022 100644 --- a/activerecord/test/cases/adapters/mysql/statement_pool_test.rb +++ b/activerecord/test/cases/adapters/mysql/statement_pool_test.rb @@ -3,7 +3,7 @@ require 'cases/helper' class MysqlStatementPoolTest < ActiveRecord::MysqlTestCase if Process.respond_to?(:fork) def test_cache_is_per_pid - cache = ActiveRecord::ConnectionAdapters::MysqlAdapter::StatementPool.new nil, 10 + cache = ActiveRecord::ConnectionAdapters::MysqlAdapter::StatementPool.new(10) cache['foo'] = 'bar' assert_equal 'bar', cache['foo'] diff --git a/activerecord/test/cases/adapters/mysql/unsigned_type_test.rb b/activerecord/test/cases/adapters/mysql/unsigned_type_test.rb index ed9398a918..84c5394c2e 100644 --- a/activerecord/test/cases/adapters/mysql/unsigned_type_test.rb +++ b/activerecord/test/cases/adapters/mysql/unsigned_type_test.rb @@ -1,6 +1,8 @@ require "cases/helper" +require "support/schema_dumping_helper" class MysqlUnsignedTypeTest < ActiveRecord::MysqlTestCase + include SchemaDumpingHelper self.use_transactional_tests = false class UnsignedType < ActiveRecord::Base @@ -9,12 +11,15 @@ class MysqlUnsignedTypeTest < ActiveRecord::MysqlTestCase setup do @connection = ActiveRecord::Base.connection @connection.create_table("unsigned_types", force: true) do |t| - t.column :unsigned_integer, "int unsigned" + t.integer :unsigned_integer, unsigned: true + t.bigint :unsigned_bigint, unsigned: true + t.float :unsigned_float, unsigned: true + t.decimal :unsigned_decimal, unsigned: true, precision: 10, scale: 2 end end teardown do - @connection.drop_table "unsigned_types" + @connection.drop_table "unsigned_types", if_exists: true end test "unsigned int max value is in range" do @@ -26,5 +31,35 @@ class MysqlUnsignedTypeTest < ActiveRecord::MysqlTestCase assert_raise(RangeError) do UnsignedType.create(unsigned_integer: -10) end + assert_raise(RangeError) do + UnsignedType.create(unsigned_bigint: -10) + end + assert_raise(ActiveRecord::StatementInvalid) do + UnsignedType.create(unsigned_float: -10.0) + end + assert_raise(ActiveRecord::StatementInvalid) do + UnsignedType.create(unsigned_decimal: -10.0) + end + end + + test "schema definition can use unsigned as the type" do + @connection.change_table("unsigned_types") do |t| + t.unsigned_integer :unsigned_integer_t + t.unsigned_bigint :unsigned_bigint_t + t.unsigned_float :unsigned_float_t + t.unsigned_decimal :unsigned_decimal_t, precision: 10, scale: 2 + end + + @connection.columns("unsigned_types").select { |c| /^unsigned_/ === c.name }.each do |column| + assert column.unsigned? + end + end + + test "schema dump includes unsigned option" do + schema = dump_table_schema "unsigned_types" + assert_match %r{t.integer\s+"unsigned_integer",\s+limit: 4,\s+unsigned: true$}, schema + assert_match %r{t.integer\s+"unsigned_bigint",\s+limit: 8,\s+unsigned: true$}, schema + assert_match %r{t.float\s+"unsigned_float",\s+limit: 24,\s+unsigned: true$}, schema + assert_match %r{t.decimal\s+"unsigned_decimal",\s+precision: 10,\s+scale: 2,\s+unsigned: true$}, schema end end diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb index 6558d60aa1..31dc69a45b 100644 --- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb @@ -100,17 +100,15 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase assert_equal "DROP TABLE `people`", drop_table(:people) end - if current_adapter?(:Mysql2Adapter) - def test_create_mysql_database_with_encoding - assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8`", create_database(:matt) - assert_equal "CREATE DATABASE `aimonetti` DEFAULT CHARACTER SET `latin1`", create_database(:aimonetti, {:charset => 'latin1'}) - assert_equal "CREATE DATABASE `matt_aimonetti` DEFAULT CHARACTER SET `big5` COLLATE `big5_chinese_ci`", create_database(:matt_aimonetti, {:charset => :big5, :collation => :big5_chinese_ci}) - end + def test_create_mysql_database_with_encoding + assert_equal "CREATE DATABASE `matt` DEFAULT CHARACTER SET `utf8`", create_database(:matt) + assert_equal "CREATE DATABASE `aimonetti` DEFAULT CHARACTER SET `latin1`", create_database(:aimonetti, {:charset => 'latin1'}) + assert_equal "CREATE DATABASE `matt_aimonetti` DEFAULT CHARACTER SET `big5` COLLATE `big5_chinese_ci`", create_database(:matt_aimonetti, {:charset => :big5, :collation => :big5_chinese_ci}) + end - def test_recreate_mysql_database_with_encoding - create_database(:luca, {:charset => 'latin1'}) - assert_equal "CREATE DATABASE `luca` DEFAULT CHARACTER SET `latin1`", recreate_database(:luca, {:charset => 'latin1'}) - end + def test_recreate_mysql_database_with_encoding + create_database(:luca, {:charset => 'latin1'}) + assert_equal "CREATE DATABASE `luca` DEFAULT CHARACTER SET `latin1`", recreate_database(:luca, {:charset => 'latin1'}) end def test_add_column diff --git a/activerecord/test/cases/adapters/mysql2/schema_test.rb b/activerecord/test/cases/adapters/mysql2/schema_test.rb index 880a2123d2..faf2acb9cb 100644 --- a/activerecord/test/cases/adapters/mysql2/schema_test.rb +++ b/activerecord/test/cases/adapters/mysql2/schema_test.rb @@ -36,14 +36,6 @@ module ActiveRecord assert(!@connection.table_exists?("#{@db_name}.zomg"), "table should not exist") end - def test_tables_quoting - @connection.tables(nil, "foo-bar", nil) - flunk - rescue => e - # assertion for *quoted* database properly - assert_match(/database 'foo-bar'/, e.inspect) - end - def test_dump_indexes index_a_name = 'index_key_tests_on_snack' index_b_name = 'index_key_tests_on_pizza' diff --git a/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb b/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb index 9e06db2519..a6f6dd21bb 100644 --- a/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb +++ b/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb @@ -1,6 +1,8 @@ require "cases/helper" +require "support/schema_dumping_helper" class Mysql2UnsignedTypeTest < ActiveRecord::Mysql2TestCase + include SchemaDumpingHelper self.use_transactional_tests = false class UnsignedType < ActiveRecord::Base @@ -9,12 +11,15 @@ class Mysql2UnsignedTypeTest < ActiveRecord::Mysql2TestCase setup do @connection = ActiveRecord::Base.connection @connection.create_table("unsigned_types", force: true) do |t| - t.column :unsigned_integer, "int unsigned" + t.integer :unsigned_integer, unsigned: true + t.bigint :unsigned_bigint, unsigned: true + t.float :unsigned_float, unsigned: true + t.decimal :unsigned_decimal, unsigned: true, precision: 10, scale: 2 end end teardown do - @connection.drop_table "unsigned_types" + @connection.drop_table "unsigned_types", if_exists: true end test "unsigned int max value is in range" do @@ -26,5 +31,35 @@ class Mysql2UnsignedTypeTest < ActiveRecord::Mysql2TestCase assert_raise(RangeError) do UnsignedType.create(unsigned_integer: -10) end + assert_raise(RangeError) do + UnsignedType.create(unsigned_bigint: -10) + end + assert_raise(ActiveRecord::StatementInvalid) do + UnsignedType.create(unsigned_float: -10.0) + end + assert_raise(ActiveRecord::StatementInvalid) do + UnsignedType.create(unsigned_decimal: -10.0) + end + end + + test "schema definition can use unsigned as the type" do + @connection.change_table("unsigned_types") do |t| + t.unsigned_integer :unsigned_integer_t + t.unsigned_bigint :unsigned_bigint_t + t.unsigned_float :unsigned_float_t + t.unsigned_decimal :unsigned_decimal_t, precision: 10, scale: 2 + end + + @connection.columns("unsigned_types").select { |c| /^unsigned_/ === c.name }.each do |column| + assert column.unsigned? + end + end + + test "schema dump includes unsigned option" do + schema = dump_table_schema "unsigned_types" + assert_match %r{t.integer\s+"unsigned_integer",\s+limit: 4,\s+unsigned: true$}, schema + assert_match %r{t.integer\s+"unsigned_bigint",\s+limit: 8,\s+unsigned: true$}, schema + assert_match %r{t.float\s+"unsigned_float",\s+limit: 24,\s+unsigned: true$}, schema + assert_match %r{t.decimal\s+"unsigned_decimal",\s+precision: 10,\s+scale: 2,\s+unsigned: true$}, schema end end diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb index f242f32496..b3b121b4fb 100644 --- a/activerecord/test/cases/adapters/postgresql/json_test.rb +++ b/activerecord/test/cases/adapters/postgresql/json_test.rb @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- require "cases/helper" require 'support/schema_dumping_helper' diff --git a/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb b/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb index ef324183a7..559b951109 100644 --- a/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb @@ -6,7 +6,7 @@ module ActiveRecord::ConnectionAdapters if Process.respond_to?(:fork) def test_cache_is_per_pid - cache = StatementPool.new nil, 10 + cache = StatementPool.new(10) cache['foo'] = 'bar' assert_equal 'bar', cache['foo'] diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 7de5bc01ae..0beaf0056a 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -1482,6 +1482,25 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert firm.companies.exists?(:name => 'child') end + def test_restrict_with_error_with_locale + I18n.backend = I18n::Backend::Simple.new + I18n.backend.store_translations 'en', activerecord: {attributes: {restricted_with_error_firm: {companies: 'client companies'}}} + firm = RestrictedWithErrorFirm.create!(name: 'restrict') + firm.companies.create(name: 'child') + + assert !firm.companies.empty? + + firm.destroy + + assert !firm.errors.empty? + + assert_equal "Cannot delete record because dependent client companies exist", firm.errors[:base].first + assert RestrictedWithErrorFirm.exists?(name: 'restrict') + assert firm.companies.exists?(name: 'child') + ensure + I18n.backend.reload! + end + def test_included_in_collection assert_equal true, companies(:first_firm).clients.include?(Client.find(2)) end diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index d46e7ad235..c9d9e29f09 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -219,6 +219,24 @@ class HasOneAssociationsTest < ActiveRecord::TestCase assert firm.account.present? end + def test_restrict_with_error_with_locale + I18n.backend = I18n::Backend::Simple.new + I18n.backend.store_translations 'en', activerecord: {attributes: {restricted_with_error_firm: {account: 'firm account'}}} + firm = RestrictedWithErrorFirm.create!(name: 'restrict') + firm.create_account(credit_limit: 10) + + assert_not_nil firm.account + + firm.destroy + + assert !firm.errors.empty? + assert_equal "Cannot delete record because a dependent firm account exists", firm.errors[:base].first + assert RestrictedWithErrorFirm.exists?(name: 'restrict') + assert firm.account.present? + ensure + I18n.backend.reload! + end + def test_successful_build_association firm = Firm.new("name" => "GlobalMegaCorp") firm.save diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index e2608e3670..b67201d94d 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -175,7 +175,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_equal category_attrs , category.attributes_before_type_cast end - if current_adapter?(:MysqlAdapter) + if current_adapter?(:MysqlAdapter, :Mysql2Adapter) def test_read_attributes_before_type_cast_on_boolean bool = Boolean.create({ "value" => false }) if RUBY_PLATFORM =~ /java/ diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 0edb8a8901..3aa0849cb5 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - require "cases/helper" require 'models/post' require 'models/author' @@ -1299,9 +1297,10 @@ class BasicsTest < ActiveRecord::TestCase end def test_compute_type_no_method_error - ActiveSupport::Dependencies.stubs(:safe_constantize).raises(NoMethodError) - assert_raises NoMethodError do - ActiveRecord::Base.send :compute_type, 'InvalidModel' + ActiveSupport::Dependencies.stub(:safe_constantize, proc{ raise NoMethodError }) do + assert_raises NoMethodError do + ActiveRecord::Base.send :compute_type, 'InvalidModel' + end end end @@ -1315,18 +1314,20 @@ class BasicsTest < ActiveRecord::TestCase error = e end - ActiveSupport::Dependencies.stubs(:safe_constantize).raises(e) + ActiveSupport::Dependencies.stub(:safe_constantize, proc{ raise e }) do - exception = assert_raises NameError do - ActiveRecord::Base.send :compute_type, 'InvalidModel' + exception = assert_raises NameError do + ActiveRecord::Base.send :compute_type, 'InvalidModel' + end + assert_equal error.message, exception.message end - assert_equal error.message, exception.message end def test_compute_type_argument_error - ActiveSupport::Dependencies.stubs(:safe_constantize).raises(ArgumentError) - assert_raises ArgumentError do - ActiveRecord::Base.send :compute_type, 'InvalidModel' + ActiveSupport::Dependencies.stub(:safe_constantize, proc{ raise ArgumentError }) do + assert_raises ArgumentError do + ActiveRecord::Base.send :compute_type, 'InvalidModel' + end end end diff --git a/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb b/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb index 80244d1439..2749273884 100644 --- a/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb +++ b/activerecord/test/cases/connection_adapters/mysql_type_lookup_test.rb @@ -22,6 +22,10 @@ module ActiveRecord assert_lookup_type :string, "SET('one', 'two', 'three')" end + def test_set_type_with_value_matching_other_type + assert_lookup_type :string, "SET('unicode', '8bit', 'none', 'time')" + end + def test_enum_type_with_value_matching_other_type assert_lookup_type :string, "ENUM('unicode', '8bit', 'none')" end diff --git a/activerecord/test/cases/explain_test.rb b/activerecord/test/cases/explain_test.rb index f1d5511bb8..64dfd86ce2 100644 --- a/activerecord/test/cases/explain_test.rb +++ b/activerecord/test/cases/explain_test.rb @@ -39,38 +39,49 @@ if ActiveRecord::Base.connection.supports_explain? binds = [[], []] queries = sqls.zip(binds) - connection.stubs(:explain).returns('query plan foo', 'query plan bar') - expected = sqls.map {|sql| "EXPLAIN for: #{sql}\nquery plan #{sql}"}.join("\n") - assert_equal expected, base.exec_explain(queries) + stub_explain_for_query_plans do + expected = sqls.map {|sql| "EXPLAIN for: #{sql}\nquery plan #{sql}"}.join("\n") + assert_equal expected, base.exec_explain(queries) + end end def test_exec_explain_with_binds - cols = [Object.new, Object.new] - cols[0].expects(:name).returns('wadus') - cols[1].expects(:name).returns('chaflan') + object = Struct.new(:name) + cols = [object.new('wadus'), object.new('chaflan')] sqls = %w(foo bar) binds = [[[cols[0], 1]], [[cols[1], 2]]] queries = sqls.zip(binds) - connection.stubs(:explain).returns("query plan foo\n", "query plan bar\n") - expected = <<-SQL.strip_heredoc - EXPLAIN for: #{sqls[0]} [["wadus", 1]] - query plan foo + stub_explain_for_query_plans(["query plan foo\n", "query plan bar\n"]) do + expected = <<-SQL.strip_heredoc + EXPLAIN for: #{sqls[0]} [["wadus", 1]] + query plan foo - EXPLAIN for: #{sqls[1]} [["chaflan", 2]] - query plan bar - SQL - assert_equal expected, base.exec_explain(queries) + EXPLAIN for: #{sqls[1]} [["chaflan", 2]] + query plan bar + SQL + assert_equal expected, base.exec_explain(queries) + end end def test_unsupported_connection_adapter - connection.stubs(:supports_explain?).returns(false) + connection.stub(:supports_explain?, false) do + assert_not_called(base.logger, :warn) do + Car.where(:name => 'honda').to_a + end + end + end - base.logger.expects(:warn).never + private - Car.where(:name => 'honda').to_a - end + def stub_explain_for_query_plans(query_plans = ['query plan foo', 'query plan bar']) + explain_called = 0 + + connection.stub(:explain, proc{ explain_called += 1; query_plans[explain_called - 1] }) do + yield + end + end end end diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 4b819a82e8..0dc884497c 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -178,8 +178,9 @@ class FinderTest < ActiveRecord::TestCase end def test_exists_does_not_instantiate_records - Developer.expects(:instantiate).never - Developer.exists? + assert_not_called(Developer, :instantiate) do + Developer.exists? + end end def test_find_by_array_of_one_id diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index 64759160dc..e3c4bd9f4a 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -408,9 +408,11 @@ class FixturesWithoutInstantiationTest < ActiveRecord::TestCase end def test_reloading_fixtures_through_accessor_methods + topic = Struct.new(:title) assert_equal "The First Topic", topics(:first).title - @loaded_fixtures['topics']['first'].expects(:find).returns(stub(:title => "Fresh Topic!")) - assert_equal "Fresh Topic!", topics(:first, true).title + assert_called(@loaded_fixtures['topics']['first'], :find, returns: topic.new("Fresh Topic!")) do + assert_equal "Fresh Topic!", topics(:first, true).title + end end end diff --git a/activerecord/test/cases/migration/command_recorder_test.rb b/activerecord/test/cases/migration/command_recorder_test.rb index 99f1dc65b0..1e3529db54 100644 --- a/activerecord/test/cases/migration/command_recorder_test.rb +++ b/activerecord/test/cases/migration/command_recorder_test.rb @@ -31,7 +31,8 @@ module ActiveRecord end def test_unknown_commands_delegate - recorder = CommandRecorder.new(stub(:foo => 'bar')) + recorder = Struct.new(:foo) + recorder = CommandRecorder.new(recorder.new('bar')) assert_equal 'bar', recorder.foo end diff --git a/activerecord/test/cases/migration/references_foreign_key_test.rb b/activerecord/test/cases/migration/references_foreign_key_test.rb index 1594f99852..4f0da999d8 100644 --- a/activerecord/test/cases/migration/references_foreign_key_test.rb +++ b/activerecord/test/cases/migration/references_foreign_key_test.rb @@ -32,6 +32,14 @@ module ActiveRecord assert_equal [], @connection.foreign_keys("testings") end + test "foreign keys can be created in one query" do + assert_queries(1) do + @connection.create_table :testings do |t| + t.references :testing_parent, foreign_key: true + end + end + end + test "options hash can be passed" do @connection.change_table :testing_parents do |t| t.integer :other_id diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index b8a0401fe3..93cb631a04 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -273,10 +273,11 @@ class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase end def test_should_modify_an_existing_record_if_there_is_a_matching_composite_id - @ship.stubs(:id).returns('ABC1X') - @pirate.ship_attributes = { :id => @ship.id, :name => 'Davy Jones Gold Dagger' } + @ship.stub(:id, 'ABC1X') do + @pirate.ship_attributes = { :id => @ship.id, :name => 'Davy Jones Gold Dagger' } - assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name + assert_equal 'Davy Jones Gold Dagger', @pirate.ship.name + end end def test_should_destroy_an_existing_record_if_there_is_a_matching_id_and_destroy_is_truthy @@ -457,10 +458,11 @@ class TestNestedAttributesOnABelongsToAssociation < ActiveRecord::TestCase end def test_should_modify_an_existing_record_if_there_is_a_matching_composite_id - @pirate.stubs(:id).returns('ABC1X') - @ship.pirate_attributes = { :id => @pirate.id, :catchphrase => 'Arr' } + @pirate.stub(:id, 'ABC1X') do + @ship.pirate_attributes = { :id => @pirate.id, :catchphrase => 'Arr' } - assert_equal 'Arr', @ship.pirate.catchphrase + assert_equal 'Arr', @ship.pirate.catchphrase + end end def test_should_destroy_an_existing_record_if_there_is_a_matching_id_and_destroy_is_truthy @@ -638,17 +640,19 @@ module NestedAttributesOnACollectionAssociationTests end def test_should_take_a_hash_with_composite_id_keys_and_assign_the_attributes_to_the_associated_models - @child_1.stubs(:id).returns('ABC1X') - @child_2.stubs(:id).returns('ABC2X') - - @pirate.attributes = { - association_getter => [ - { :id => @child_1.id, :name => 'Grace OMalley' }, - { :id => @child_2.id, :name => 'Privateers Greed' } - ] - } + @child_1.stub(:id, 'ABC1X') do + @child_2.stub(:id, 'ABC2X') do + + @pirate.attributes = { + association_getter => [ + { :id => @child_1.id, :name => 'Grace OMalley' }, + { :id => @child_2.id, :name => 'Privateers Greed' } + ] + } - assert_equal ['Grace OMalley', 'Privateers Greed'], [@child_1.name, @child_2.name] + assert_equal ['Grace OMalley', 'Privateers Greed'], [@child_1.name, @child_2.name] + end + end end def test_should_raise_RecordNotFound_if_an_id_is_given_but_doesnt_return_a_record diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb index 0745a37ee9..5e4ba47988 100644 --- a/activerecord/test/cases/primary_keys_test.rb +++ b/activerecord/test/cases/primary_keys_test.rb @@ -241,6 +241,33 @@ class PrimaryKeyAnyTypeTest < ActiveRecord::TestCase end end +class CompositePrimaryKeyTest < ActiveRecord::TestCase + include SchemaDumpingHelper + + self.use_transactional_tests = false + + def setup + @connection = ActiveRecord::Base.connection + @connection.create_table(:barcodes, primary_key: ["region", "code"], force: true) do |t| + t.string :region + t.integer :code + end + end + + def teardown + @connection.drop_table(:barcodes, if_exists: true) + end + + def test_composite_primary_key + assert_equal ["region", "code"], @connection.primary_keys("barcodes") + end + + def test_collectly_dump_composite_primary_key + schema = dump_table_schema "barcodes" + assert_match %r{create_table "barcodes", primary_key: \["region", "code"\]}, schema + end +end + if current_adapter?(:MysqlAdapter, :Mysql2Adapter) class PrimaryKeyWithAnsiQuotesTest < ActiveRecord::TestCase self.use_transactional_tests = false @@ -300,11 +327,12 @@ if current_adapter?(:PostgreSQLAdapter, :MysqlAdapter, :Mysql2Adapter) if current_adapter?(:MysqlAdapter, :Mysql2Adapter) test "primary key column type with options" do - @connection.create_table(:widgets, id: :primary_key, limit: 8, force: true) + @connection.create_table(:widgets, id: :primary_key, limit: 8, unsigned: true, force: true) column = @connection.columns(:widgets).find { |c| c.name == 'id' } assert column.auto_increment? assert_equal :integer, column.type assert_equal 8, column.limit + assert column.unsigned? end end end diff --git a/activerecord/test/cases/relation/mutation_test.rb b/activerecord/test/cases/relation/mutation_test.rb index ba4d9d2503..88d2dd55ab 100644 --- a/activerecord/test/cases/relation/mutation_test.rb +++ b/activerecord/test/cases/relation/mutation_test.rb @@ -55,9 +55,10 @@ module ActiveRecord test '#order! on non-string does not attempt regexp match for references' do obj = Object.new - obj.expects(:=~).never - assert relation.order!(obj) - assert_equal [obj], relation.order_values + assert_not_called(obj, :=~) do + assert relation.order!(obj) + assert_equal [obj], relation.order_values + end end test '#references!' do diff --git a/activerecord/test/cases/test_case.rb b/activerecord/test/cases/test_case.rb index 7761ea5612..47e664f4e7 100644 --- a/activerecord/test/cases/test_case.rb +++ b/activerecord/test/cases/test_case.rb @@ -103,7 +103,7 @@ module ActiveRecord # ignored SQL, or better yet, use a different notification for the queries # instead examining the SQL content. oracle_ignored = [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im, /^\s*select .* from all_constraints/im, /^\s*select .* from all_tab_cols/im] - mysql_ignored = [/^SHOW TABLES/i, /^SHOW FULL FIELDS/, /^SHOW CREATE TABLE /i, /^SHOW VARIABLES /] + mysql_ignored = [/^SHOW FULL TABLES/i, /^SHOW FULL FIELDS/, /^SHOW CREATE TABLE /i, /^SHOW VARIABLES /, /^\s*SELECT (?:column_name|table_name)\b.*\bFROM information_schema\.(?:key_column_usage|tables)\b/im] postgresql_ignored = [/^\s*select\b.*\bfrom\b.*pg_namespace\b/im, /^\s*select tablename\b.*from pg_tables\b/im, /^\s*select\b.*\battname\b.*\bfrom\b.*\bpg_attribute\b/im, /^SHOW search_path/i] sqlite3_ignored = [/^\s*SELECT name\b.*\bFROM sqlite_master/im, /^\s*SELECT sql\b.*\bFROM sqlite_master/im] diff --git a/activerecord/test/cases/test_fixtures_test.rb b/activerecord/test/cases/test_fixtures_test.rb index 3f4baf8378..1970fe82d0 100644 --- a/activerecord/test/cases/test_fixtures_test.rb +++ b/activerecord/test/cases/test_fixtures_test.rb @@ -28,7 +28,7 @@ class TestFixturesTest < ActiveRecord::TestCase assert_equal true, @klass.use_transactional_tests end - def test_use_transactional_tests_can_be_overriden + def test_use_transactional_tests_can_be_overridden @klass.use_transactional_tests = "foobar" assert_equal "foobar", @klass.use_transactional_tests diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb index 29a6ec7522..ec5bdfd725 100644 --- a/activerecord/test/cases/transactions_test.rb +++ b/activerecord/test/cases/transactions_test.rb @@ -487,13 +487,17 @@ class TransactionTest < ActiveRecord::TestCase end def test_rollback_when_commit_raises - Topic.connection.expects(:begin_db_transaction) - Topic.connection.expects(:commit_db_transaction).raises('OH NOES') - Topic.connection.expects(:rollback_db_transaction) + assert_called(Topic.connection, :begin_db_transaction) do + Topic.connection.stub(:commit_db_transaction, ->{ raise('OH NOES') }) do + assert_called(Topic.connection, :rollback_db_transaction) do - assert_raise RuntimeError do - Topic.transaction do - # do nothing + e = assert_raise RuntimeError do + Topic.transaction do + # do nothing + end + end + assert_equal 'OH NOES', e.message + end end end end diff --git a/activerecord/test/cases/validations/length_validation_test.rb b/activerecord/test/cases/validations/length_validation_test.rb index f95f8f0b8f..c5d8f8895c 100644 --- a/activerecord/test/cases/validations/length_validation_test.rb +++ b/activerecord/test/cases/validations/length_validation_test.rb @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- require "cases/helper" require 'models/owner' require 'models/pet' diff --git a/activerecord/test/cases/view_test.rb b/activerecord/test/cases/view_test.rb index 1eb1430065..d7b3e223ed 100644 --- a/activerecord/test/cases/view_test.rb +++ b/activerecord/test/cases/view_test.rb @@ -1,7 +1,9 @@ require "cases/helper" require "models/book" +require "support/schema_dumping_helper" module ViewBehavior + include SchemaDumpingHelper extend ActiveSupport::Concern included do @@ -31,6 +33,15 @@ module ViewBehavior assert_equal ["Ruby for Rails"], books.map(&:name) end + def test_views + assert_equal [Ebook.table_name], @connection.views + end + + def test_view_exists + view_name = Ebook.table_name + assert @connection.view_exists?(view_name), "'#{view_name}' view should exist" + end + def test_table_exists view_name = Ebook.table_name assert @connection.table_exists?(view_name), "'#{view_name}' table should exist" @@ -53,6 +64,11 @@ module ViewBehavior end assert_nil model.primary_key end + + def test_does_not_dump_view_as_table + schema = dump_table_schema "ebooks" + assert_no_match %r{create_table "ebooks"}, schema + end end if ActiveRecord::Base.connection.supports_views? @@ -70,6 +86,7 @@ class ViewWithPrimaryKeyTest < ActiveRecord::TestCase end class ViewWithoutPrimaryKeyTest < ActiveRecord::TestCase + include SchemaDumpingHelper fixtures :books class Paperback < ActiveRecord::Base; end @@ -91,6 +108,15 @@ class ViewWithoutPrimaryKeyTest < ActiveRecord::TestCase assert_equal ["Agile Web Development with Rails"], books.map(&:name) end + def test_views + assert_equal [Paperback.table_name], @connection.views + end + + def test_view_exists + view_name = Paperback.table_name + assert @connection.view_exists?(view_name), "'#{view_name}' view should exist" + end + def test_table_exists view_name = Paperback.table_name assert @connection.table_exists?(view_name), "'#{view_name}' table should exist" @@ -109,6 +135,11 @@ class ViewWithoutPrimaryKeyTest < ActiveRecord::TestCase def test_does_not_have_a_primary_key assert_nil Paperback.primary_key end + + def test_does_not_dump_view_as_table + schema = dump_table_schema "paperbacks" + assert_no_match %r{create_table "paperbacks"}, schema + end end # sqlite dose not support CREATE, INSERT, and DELETE for VIEW diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 994ece9244..df1834d381 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -1,4 +1,3 @@ - ActiveRecord::Schema.define do def except(adapter_names_to_exclude) unless [adapter_names_to_exclude].flatten.include?(adapter_name) diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index addf5d521a..69413fef47 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,14 @@ +* Short-circuit `blank?` on date and time values since they are never blank. + + Fixes #21657 + + *Andrew White* + +* Replaced deprecated `ThreadSafe::Cache` with its successor `Concurrent::Map` now that + the thread_safe gem has been merged into concurrent-ruby. + + *Jerry D'Antonio* + * Updated Unicode version to 8.0.0 *Anshul Sharma* diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec index 67b6663915..d236dbd200 100644 --- a/activesupport/activesupport.gemspec +++ b/activesupport/activesupport.gemspec @@ -24,7 +24,6 @@ Gem::Specification.new do |s| s.add_dependency 'json', '~> 1.7', '>= 1.7.7' 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.1' + s.add_dependency 'concurrent-ruby', '~> 1.0.0.pre2', '< 2.0.0' s.add_dependency 'method_source' end diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index fefba5b0fd..80c5fdba17 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -126,14 +126,10 @@ module ActiveSupport def self.build(callback_sequence, user_callback, user_conditions, chain_config, filter) halted_lambda = chain_config[:terminator] - if chain_config.key?(:terminator) && user_conditions.any? + if user_conditions.any? halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter) - elsif chain_config.key? :terminator - halting(callback_sequence, user_callback, halted_lambda, filter) - elsif user_conditions.any? - conditional(callback_sequence, user_callback, user_conditions) else - simple callback_sequence, user_callback + halting(callback_sequence, user_callback, halted_lambda, filter) end end @@ -175,42 +171,15 @@ module ActiveSupport end end private_class_method :halting - - def self.conditional(callback_sequence, user_callback, user_conditions) - callback_sequence.before do |env| - target = env.target - value = env.value - - if user_conditions.all? { |c| c.call(target, value) } - user_callback.call target, value - end - - env - end - end - private_class_method :conditional - - def self.simple(callback_sequence, user_callback) - callback_sequence.before do |env| - user_callback.call env.target, env.value - - env - end - end - private_class_method :simple end class After def self.build(callback_sequence, user_callback, user_conditions, chain_config) if chain_config[:skip_after_callbacks_if_terminated] - if chain_config.key?(:terminator) && user_conditions.any? + if user_conditions.any? halting_and_conditional(callback_sequence, user_callback, user_conditions) - elsif chain_config.key?(:terminator) - halting(callback_sequence, user_callback) - elsif user_conditions.any? - conditional callback_sequence, user_callback, user_conditions else - simple callback_sequence, user_callback + halting(callback_sequence, user_callback) end else if user_conditions.any? @@ -273,14 +242,10 @@ module ActiveSupport class Around def self.build(callback_sequence, user_callback, user_conditions, chain_config) - if chain_config.key?(:terminator) && user_conditions.any? + if user_conditions.any? halting_and_conditional(callback_sequence, user_callback, user_conditions) - elsif chain_config.key? :terminator - halting(callback_sequence, user_callback) - elsif user_conditions.any? - conditional(callback_sequence, user_callback, user_conditions) else - simple(callback_sequence, user_callback) + halting(callback_sequence, user_callback) end end @@ -318,33 +283,6 @@ module ActiveSupport end end private_class_method :halting - - def self.conditional(callback_sequence, user_callback, user_conditions) - callback_sequence.around do |env, &run| - target = env.target - value = env.value - - if user_conditions.all? { |c| c.call(target, value) } - user_callback.call(target, value) { - run.call.value - } - env - else - run.call - end - end - end - private_class_method :conditional - - def self.simple(callback_sequence, user_callback) - callback_sequence.around do |env, &run| - user_callback.call(env.target, env.value) { - run.call.value - } - env - end - end - private_class_method :simple end end diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb index d80df21e7d..c5cc31abc5 100644 --- a/activesupport/lib/active_support/core_ext/array/conversions.rb +++ b/activesupport/lib/active_support/core_ext/array/conversions.rb @@ -32,7 +32,7 @@ class Array # ['one', 'two', 'three'].to_sentence # => "one, two, and three" # # ['one', 'two'].to_sentence(passing: 'invalid option') - # # => ArgumentError: Unknown key :passing + # # => ArgumentError: Unknown key: :passing. Valid keys are: :words_connector, :two_words_connector, :last_word_connector, :locale # # ['one', 'two'].to_sentence(two_words_connector: '-') # # => "one-two" diff --git a/activesupport/lib/active_support/core_ext/date.rb b/activesupport/lib/active_support/core_ext/date.rb index 465fedda80..7f0f4639a2 100644 --- a/activesupport/lib/active_support/core_ext/date.rb +++ b/activesupport/lib/active_support/core_ext/date.rb @@ -1,5 +1,5 @@ require 'active_support/core_ext/date/acts_like' +require 'active_support/core_ext/date/blank' require 'active_support/core_ext/date/calculations' require 'active_support/core_ext/date/conversions' require 'active_support/core_ext/date/zones' - diff --git a/activesupport/lib/active_support/core_ext/date/blank.rb b/activesupport/lib/active_support/core_ext/date/blank.rb new file mode 100644 index 0000000000..71627b6a6f --- /dev/null +++ b/activesupport/lib/active_support/core_ext/date/blank.rb @@ -0,0 +1,12 @@ +require 'date' + +class Date #:nodoc: + # No Date is blank: + # + # Date.today.blank? # => false + # + # @return [false] + def blank? + false + end +end diff --git a/activesupport/lib/active_support/core_ext/date/conversions.rb b/activesupport/lib/active_support/core_ext/date/conversions.rb index 31479a1269..ed8bca77ac 100644 --- a/activesupport/lib/active_support/core_ext/date/conversions.rb +++ b/activesupport/lib/active_support/core_ext/date/conversions.rb @@ -75,10 +75,10 @@ class Date # # date = Date.new(2007, 11, 10) # => Sat, 10 Nov 2007 # - # date.to_time # => Sat Nov 10 00:00:00 0800 2007 - # date.to_time(:local) # => Sat Nov 10 00:00:00 0800 2007 + # date.to_time # => 2007-11-10 00:00:00 0800 + # date.to_time(:local) # => 2007-11-10 00:00:00 0800 # - # date.to_time(:utc) # => Sat Nov 10 00:00:00 UTC 2007 + # date.to_time(:utc) # => 2007-11-10 00:00:00 UTC def to_time(form = :local) ::Time.send(form, year, month, day) end diff --git a/activesupport/lib/active_support/core_ext/date_and_time/zones.rb b/activesupport/lib/active_support/core_ext/date_and_time/zones.rb index 96c6df9407..d29a8db5cf 100644 --- a/activesupport/lib/active_support/core_ext/date_and_time/zones.rb +++ b/activesupport/lib/active_support/core_ext/date_and_time/zones.rb @@ -4,7 +4,7 @@ module DateAndTime # if Time.zone_default is set. Otherwise, it returns the current time. # # Time.zone = 'Hawaii' # => 'Hawaii' - # DateTime.utc(2000).in_time_zone # => Fri, 31 Dec 1999 14:00:00 HST -10:00 + # Time.utc(2000).in_time_zone # => Fri, 31 Dec 1999 14:00:00 HST -10:00 # Date.new(2000).in_time_zone # => Sat, 01 Jan 2000 00:00:00 HST -10:00 # # This method is similar to Time#localtime, except that it uses <tt>Time.zone</tt> as the local zone @@ -14,7 +14,6 @@ module DateAndTime # and the conversion will be based on that zone instead of <tt>Time.zone</tt>. # # Time.utc(2000).in_time_zone('Alaska') # => Fri, 31 Dec 1999 15:00:00 AKST -09:00 - # DateTime.utc(2000).in_time_zone('Alaska') # => Fri, 31 Dec 1999 15:00:00 AKST -09:00 # Date.new(2000).in_time_zone('Alaska') # => Sat, 01 Jan 2000 00:00:00 AKST -09:00 def in_time_zone(zone = ::Time.zone) time_zone = ::Time.find_zone! zone diff --git a/activesupport/lib/active_support/core_ext/date_time.rb b/activesupport/lib/active_support/core_ext/date_time.rb index e8a27b9f38..bcb228b09a 100644 --- a/activesupport/lib/active_support/core_ext/date_time.rb +++ b/activesupport/lib/active_support/core_ext/date_time.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/date_time/acts_like' +require 'active_support/core_ext/date_time/blank' require 'active_support/core_ext/date_time/calculations' require 'active_support/core_ext/date_time/conversions' require 'active_support/core_ext/date_time/zones' diff --git a/activesupport/lib/active_support/core_ext/date_time/blank.rb b/activesupport/lib/active_support/core_ext/date_time/blank.rb new file mode 100644 index 0000000000..56981b75fb --- /dev/null +++ b/activesupport/lib/active_support/core_ext/date_time/blank.rb @@ -0,0 +1,12 @@ +require 'date' + +class DateTime #:nodoc: + # No DateTime is ever blank: + # + # DateTime.now.blank? # => false + # + # @return [false] + def blank? + false + end +end diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb index a77da573fe..a084177b9f 100644 --- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb +++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb @@ -19,9 +19,9 @@ class Module # The attribute name must be a valid method name in Ruby. # # module Foo - # mattr_reader :"1_Badname " + # mattr_reader :"1_Badname" # end - # # => NameError: invalid attribute name + # # => NameError: invalid attribute name: 1_Badname # # If you want to opt out the creation on the instance reader method, pass # <tt>instance_reader: false</tt> or <tt>instance_accessor: false</tt>. diff --git a/activesupport/lib/active_support/core_ext/numeric/conversions.rb b/activesupport/lib/active_support/core_ext/numeric/conversions.rb index 0c8ff79237..d5bb11deed 100644 --- a/activesupport/lib/active_support/core_ext/numeric/conversions.rb +++ b/activesupport/lib/active_support/core_ext/numeric/conversions.rb @@ -41,7 +41,7 @@ class Numeric # 1000.to_s(:percentage, delimiter: '.', separator: ',') # => 1.000,000% # 302.24398923423.to_s(:percentage, precision: 5) # => 302.24399% # 1000.to_s(:percentage, locale: :fr) # => 1 000,000% - # 100.to_s(:percentage, format: '%n %') # => 100 % + # 100.to_s(:percentage, format: '%n %') # => 100.000 % # # Delimited: # 12345678.to_s(:delimited) # => 12,345,678 @@ -78,7 +78,7 @@ class Numeric # 1234567.to_s(:human_size, precision: 2) # => 1.2 MB # 483989.to_s(:human_size, precision: 2) # => 470 KB # 1234567.to_s(:human_size, precision: 2, separator: ',') # => 1,2 MB - # 1234567890123.to_s(:human_size, precision: 5) # => "1.1229 TB" + # 1234567890123.to_s(:human_size, precision: 5) # => "1.1228 TB" # 524288000.to_s(:human_size, precision: 5) # => "500 MB" # # Human-friendly format: diff --git a/activesupport/lib/active_support/core_ext/object/blank.rb b/activesupport/lib/active_support/core_ext/object/blank.rb index 548c91638c..039c50a4a2 100644 --- a/activesupport/lib/active_support/core_ext/object/blank.rb +++ b/activesupport/lib/active_support/core_ext/object/blank.rb @@ -1,5 +1,3 @@ -# encoding: utf-8 - class Object # An object is blank if it's false, empty, or a whitespace string. # For example, +false+, '', ' ', +nil+, [], and {} are all blank. @@ -129,3 +127,14 @@ class Numeric #:nodoc: false end end + +class Time #:nodoc: + # No Time is blank: + # + # Time.now.blank? # => false + # + # @return [false] + def blank? + false + end +end diff --git a/activesupport/lib/active_support/core_ext/securerandom.rb b/activesupport/lib/active_support/core_ext/securerandom.rb index 6cdbea1f37..98cf7430f7 100644 --- a/activesupport/lib/active_support/core_ext/securerandom.rb +++ b/activesupport/lib/active_support/core_ext/securerandom.rb @@ -10,8 +10,8 @@ module SecureRandom # # The result may contain alphanumeric characters except 0, O, I and l # - # p SecureRandom.base58 #=> "4kUgL2pdQMSCQtjE" - # p SecureRandom.base58(24) #=> "77TMHrHJFvFDwodq8w7Ev2m7" + # p SecureRandom.base58 # => "4kUgL2pdQMSCQtjE" + # p SecureRandom.base58(24) # => "77TMHrHJFvFDwodq8w7Ev2m7" # def self.base58(n = 16) SecureRandom.random_bytes(n).unpack("C*").map do |byte| diff --git a/activesupport/lib/active_support/core_ext/string/multibyte.rb b/activesupport/lib/active_support/core_ext/string/multibyte.rb index 7055f7f699..cc6f2158e7 100644 --- a/activesupport/lib/active_support/core_ext/string/multibyte.rb +++ b/activesupport/lib/active_support/core_ext/string/multibyte.rb @@ -9,12 +9,10 @@ class String # encapsulates the original string. A Unicode safe version of all the String methods are defined on this proxy # class. If the proxy class doesn't respond to a certain method, it's forwarded to the encapsulated string. # - # name = 'Claus Müller' - # name.reverse # => "rell??M sualC" - # name.length # => 13 - # - # name.mb_chars.reverse.to_s # => "rellüM sualC" - # name.mb_chars.length # => 12 + # >> "lj".upcase + # => "lj" + # >> "lj".mb_chars.upcase.to_s + # => "LJ" # # == Method chaining # diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb index c676b26b06..8b27ec4413 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -86,7 +86,7 @@ class ERB # use inside HTML attributes. # # If your JSON is being used downstream for insertion into the DOM, be aware of - # whether or not it is being inserted via +html()+. Most JQuery plugins do this. + # whether or not it is being inserted via +html()+. Most jQuery plugins do this. # If that is the case, be sure to +html_escape+ or +sanitize+ any user-generated # content returned by your JSON. # diff --git a/activesupport/lib/active_support/core_ext/string/strip.rb b/activesupport/lib/active_support/core_ext/string/strip.rb index 9fdd9d8d2e..55b9b87352 100644 --- a/activesupport/lib/active_support/core_ext/string/strip.rb +++ b/activesupport/lib/active_support/core_ext/string/strip.rb @@ -1,5 +1,3 @@ -require 'active_support/core_ext/object/try' - class String # Strips indentation in heredocs. # @@ -20,7 +18,6 @@ class String # 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}}/, '') + gsub(/^#{scan(/^[ \t]*(?=\S)/).min}/, ''.freeze) end end diff --git a/activesupport/lib/active_support/core_ext/uri.rb b/activesupport/lib/active_support/core_ext/uri.rb index 0b2ff817c3..c6c183edd9 100644 --- a/activesupport/lib/active_support/core_ext/uri.rb +++ b/activesupport/lib/active_support/core_ext/uri.rb @@ -1,5 +1,3 @@ -# encoding: utf-8 - require 'uri' str = "\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E" # Ni-ho-nn-go in UTF-8, means Japanese. parser = URI::Parser.new diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index 8215a3085e..16b726bcba 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -1,6 +1,6 @@ require 'set' require 'thread' -require 'thread_safe' +require 'concurrent' require 'pathname' require 'active_support/core_ext/module/aliasing' require 'active_support/core_ext/module/attribute_accessors' @@ -585,7 +585,7 @@ module ActiveSupport #:nodoc: class ClassCache def initialize - @store = ThreadSafe::Cache.new + @store = Concurrent::Map.new end def empty? diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb index 42560f3515..c3907e9c22 100644 --- a/activesupport/lib/active_support/inflector/inflections.rb +++ b/activesupport/lib/active_support/inflector/inflections.rb @@ -1,4 +1,4 @@ -require 'thread_safe' +require 'concurrent' require 'active_support/core_ext/array/prepend_and_append' require 'active_support/i18n' @@ -25,7 +25,7 @@ module ActiveSupport # singularization rules that is runs. This guarantees that your rules run # before any of the rules that may already have been loaded. class Inflections - @__instance__ = ThreadSafe::Cache.new + @__instance__ = Concurrent::Map.new class Uncountables < Array def initialize diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index ab5372ac43..595b0339cc 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -1,5 +1,3 @@ -# encoding: utf-8 - require 'active_support/inflections' module ActiveSupport diff --git a/activesupport/lib/active_support/inflector/transliterate.rb b/activesupport/lib/active_support/inflector/transliterate.rb index 7b28eeb6e2..7472d4386a 100644 --- a/activesupport/lib/active_support/inflector/transliterate.rb +++ b/activesupport/lib/active_support/inflector/transliterate.rb @@ -1,4 +1,3 @@ -# encoding: utf-8 require 'active_support/core_ext/string/multibyte' require 'active_support/i18n' @@ -76,15 +75,15 @@ module ActiveSupport parameterized_string.gsub!(/[^a-z0-9\-_]+/i, sep) unless sep.nil? || sep.empty? if sep == "-".freeze - re_duplicate_seperator = /-{2,}/ + re_duplicate_separator = /-{2,}/ re_leading_trailing_separator = /^-|-$/i else re_sep = Regexp.escape(sep) - re_duplicate_seperator = /#{re_sep}{2,}/ + re_duplicate_separator = /#{re_sep}{2,}/ re_leading_trailing_separator = /^#{re_sep}|#{re_sep}$/i end # No more than one of the separator in a row. - parameterized_string.gsub!(re_duplicate_seperator, sep) + parameterized_string.gsub!(re_duplicate_separator, sep) # Remove leading/trailing separator. parameterized_string.gsub!(re_leading_trailing_separator, ''.freeze) end diff --git a/activesupport/lib/active_support/key_generator.rb b/activesupport/lib/active_support/key_generator.rb index 51d2da3a79..6bc3db6ec6 100644 --- a/activesupport/lib/active_support/key_generator.rb +++ b/activesupport/lib/active_support/key_generator.rb @@ -1,4 +1,4 @@ -require 'thread_safe' +require 'concurrent' require 'openssl' module ActiveSupport @@ -28,7 +28,7 @@ module ActiveSupport class CachingKeyGenerator def initialize(key_generator) @key_generator = key_generator - @cache_keys = ThreadSafe::Cache.new + @cache_keys = Concurrent::Map.new end # Returns a derived key suitable for use. The default key_size is chosen diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb index 45cf6fc1ef..f6a2e7e949 100644 --- a/activesupport/lib/active_support/multibyte/chars.rb +++ b/activesupport/lib/active_support/multibyte/chars.rb @@ -1,4 +1,3 @@ -# encoding: utf-8 require 'active_support/json' require 'active_support/core_ext/string/access' require 'active_support/core_ext/string/behavior' diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb index 3f6a4c8457..5221a6dfb8 100644 --- a/activesupport/lib/active_support/multibyte/unicode.rb +++ b/activesupport/lib/active_support/multibyte/unicode.rb @@ -1,4 +1,3 @@ -# encoding: utf-8 module ActiveSupport module Multibyte module Unicode diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb index 0131fe2572..71354dd15f 100644 --- a/activesupport/lib/active_support/notifications/fanout.rb +++ b/activesupport/lib/active_support/notifications/fanout.rb @@ -1,5 +1,5 @@ require 'mutex_m' -require 'thread_safe' +require 'concurrent' module ActiveSupport module Notifications @@ -12,7 +12,7 @@ module ActiveSupport def initialize @subscribers = [] - @listeners_for = ThreadSafe::Cache.new + @listeners_for = Concurrent::Map.new super end @@ -51,7 +51,7 @@ module ActiveSupport end def listeners_for(name) - # this is correctly done double-checked locking (ThreadSafe::Cache's lookups have volatile semantics) + # this is correctly done double-checked locking (Concurrent::Map's lookups have volatile semantics) @listeners_for[name] || synchronize do # use synchronisation when accessing @subscribers @listeners_for[name] ||= @subscribers.select { |s| s.subscribed_to?(name) } diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index 817522677f..eb26eb5ee5 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -132,7 +132,7 @@ module ActiveSupport # Returns a string of the object's date, time, zone and offset from UTC. # - # Time.zone.now.httpdate # => "Thu, 04 Dec 2014 11:00:25 EST -05:00" + # Time.zone.now.inspect # => "Thu, 04 Dec 2014 11:00:25 EST -05:00" def inspect "#{time.strftime('%a, %d %b %Y %H:%M:%S')} #{zone} #{formatted_offset}" end @@ -388,6 +388,11 @@ module ActiveSupport end alias_method :kind_of?, :is_a? + # An instance of ActiveSupport::TimeWithZone is never blank + def blank? + false + end + def freeze period; utc; time # preload instance variables before freezing super diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb index 2699a064d7..4502e01b12 100644 --- a/activesupport/lib/active_support/values/time_zone.rb +++ b/activesupport/lib/active_support/values/time_zone.rb @@ -1,5 +1,5 @@ require 'tzinfo' -require 'thread_safe' +require 'concurrent' require 'active_support/core_ext/object/blank' require 'active_support/core_ext/object/try' @@ -23,7 +23,7 @@ module ActiveSupport # config.time_zone = 'Eastern Time (US & Canada)' # end # - # Time.zone # => #<TimeZone:0x514834...> + # Time.zone # => #<ActiveSupport::TimeZone:0x514834...> # Time.zone.name # => "Eastern Time (US & Canada)" # Time.zone.now # => Sun, 18 May 2008 14:30:44 EDT -04:00 # @@ -189,13 +189,13 @@ module ActiveSupport UTC_OFFSET_WITH_COLON = '%s%02d:%02d' UTC_OFFSET_WITHOUT_COLON = UTC_OFFSET_WITH_COLON.tr(':', '') - @lazy_zones_map = ThreadSafe::Cache.new + @lazy_zones_map = Concurrent::Map.new class << self # Assumes self represents an offset from UTC in seconds (as returned from # Time#utc_offset) and turns this into an +HH:MM formatted string. # - # TimeZone.seconds_to_utc_offset(-21_600) # => "-06:00" + # ActiveSupport::TimeZone.seconds_to_utc_offset(-21_600) # => "-06:00" def seconds_to_utc_offset(seconds, colon = true) format = colon ? UTC_OFFSET_WITH_COLON : UTC_OFFSET_WITHOUT_COLON sign = (seconds < 0 ? '-' : '+') diff --git a/activesupport/test/core_ext/object/blank_test.rb b/activesupport/test/core_ext/object/blank_test.rb index 8a5e385dd7..a142096993 100644 --- a/activesupport/test/core_ext/object/blank_test.rb +++ b/activesupport/test/core_ext/object/blank_test.rb @@ -1,4 +1,3 @@ - require 'abstract_unit' require 'active_support/core_ext/object/blank' diff --git a/activesupport/test/core_ext/object/try_test.rb b/activesupport/test/core_ext/object/try_test.rb index 5ea0f0eca6..25bf0207b8 100644 --- a/activesupport/test/core_ext/object/try_test.rb +++ b/activesupport/test/core_ext/object/try_test.rb @@ -121,7 +121,7 @@ class ObjectTryTest < ActiveSupport::TestCase assert_equal 5, Decorator.new(@string).size end - def test_try_with_overriden_method_on_delegator + def test_try_with_overridden_method_on_delegator assert_equal 'overridden reverse', Decorator.new(@string).reverse end diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb index 18a8b92eb9..e6898658b5 100644 --- a/activesupport/test/inflector_test_cases.rb +++ b/activesupport/test/inflector_test_cases.rb @@ -1,4 +1,3 @@ - module InflectorTestCases SingularToPlural = { "search" => "searches", diff --git a/activesupport/test/multibyte_conformance_test.rb b/activesupport/test/multibyte_conformance_test.rb index d8704716e7..2a885e32bf 100644 --- a/activesupport/test/multibyte_conformance_test.rb +++ b/activesupport/test/multibyte_conformance_test.rb @@ -1,4 +1,3 @@ - require 'abstract_unit' require 'multibyte_test_helpers' diff --git a/activesupport/test/multibyte_proxy_test.rb b/activesupport/test/multibyte_proxy_test.rb index 11f5374017..360cf57302 100644 --- a/activesupport/test/multibyte_proxy_test.rb +++ b/activesupport/test/multibyte_proxy_test.rb @@ -1,4 +1,3 @@ - require 'abstract_unit' class MultibyteProxyText < ActiveSupport::TestCase diff --git a/activesupport/test/multibyte_test_helpers.rb b/activesupport/test/multibyte_test_helpers.rb index 2e4b5cc873..58cf5488cd 100644 --- a/activesupport/test/multibyte_test_helpers.rb +++ b/activesupport/test/multibyte_test_helpers.rb @@ -1,4 +1,3 @@ - module MultibyteTestHelpers UNICODE_STRING = 'こにちわ'.freeze ASCII_STRING = 'ohayo'.freeze diff --git a/guides/rails_guides/markdown.rb b/guides/rails_guides/markdown.rb index 17035069d0..69c7cd5136 100644 --- a/guides/rails_guides/markdown.rb +++ b/guides/rails_guides/markdown.rb @@ -1,5 +1,3 @@ -# encoding: utf-8 - require 'redcarpet' require 'nokogiri' require 'rails_guides/markdown/renderer' diff --git a/guides/source/_welcome.html.erb b/guides/source/_welcome.html.erb index 67f5f1cdd5..f50bcddbe7 100644 --- a/guides/source/_welcome.html.erb +++ b/guides/source/_welcome.html.erb @@ -16,9 +16,9 @@ <% end %> <p> The guides for earlier releases: -<a href="http://guides.rubyonrails.org/v4.2.0/">Rails 4.2.0</a>, -<a href="http://guides.rubyonrails.org/v4.1.8/">Rails 4.1.8</a>, -<a href="http://guides.rubyonrails.org/v4.0.12/">Rails 4.0.12</a>, -<a href="http://guides.rubyonrails.org/v3.2.21/">Rails 3.2.21</a> and -<a href="http://guides.rubyonrails.org/v2.3.11/">Rails 2.3.11</a>. +<a href="http://guides.rubyonrails.org/v4.2/">Rails 4.2</a>, +<a href="http://guides.rubyonrails.org/v4.1/">Rails 4.1</a>, +<a href="http://guides.rubyonrails.org/v4.0/">Rails 4.0</a>, +<a href="http://guides.rubyonrails.org/v3.2/">Rails 3.2</a>, and +<a href="http://guides.rubyonrails.org/v2.3/">Rails 2.3</a>. </p> diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md index 19bdea2b8a..7e43ba375a 100644 --- a/guides/source/action_controller_overview.md +++ b/guides/source/action_controller_overview.md @@ -810,7 +810,7 @@ The [Security Guide](security.html) has more about this and a lot of other secur The Request and Response Objects -------------------------------- -In every controller there are two accessor methods pointing to the request and the response objects associated with the request cycle that is currently in execution. The `request` method contains an instance of `AbstractRequest` and the `response` method returns a response object representing what is going to be sent back to the client. +In every controller there are two accessor methods pointing to the request and the response objects associated with the request cycle that is currently in execution. The `request` method contains an instance of `ActionDispatch::Request` and the `response` method returns a response object representing what is going to be sent back to the client. ### The `request` Object @@ -1118,7 +1118,7 @@ Rails default exception handling displays a "500 Server Error" message for all e ### The Default 500 and 404 Templates -By default a production application will render either a 404 or a 500 error message. These messages are contained in static HTML files in the `public` folder, in `404.html` and `500.html` respectively. You can customize these files to add some extra information and layout, but remember that they are static; i.e. you can't use RHTML or layouts in them, just plain HTML. +By default a production application will render either a 404 or a 500 error message. These messages are contained in static HTML files in the `public` folder, in `404.html` and `500.html` respectively. You can customize these files to add some extra information and style, but remember that they are static HTML; i.e. you can't use ERB, SCSS, CoffeeScript, or layouts for them. ### `rescue_from` diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md index c39cd34e9a..4800cece82 100644 --- a/guides/source/action_mailer_basics.md +++ b/guides/source/action_mailer_basics.md @@ -760,8 +760,8 @@ config.action_mailer.smtp_settings = { enable_starttls_auto: true } ``` Note: As of July 15, 2014, Google increased [its security measures](https://support.google.com/accounts/answer/6010255) and now blocks attempts from apps it deems less secure. -You can change your gmail settings [here](https://www.google.com/settings/security/lesssecureapps) to allow the attempts or -use another ESP to send email by replacing 'smpt.gmail.com' above with the address of your provider. +You can change your gmail settings [here](https://www.google.com/settings/security/lesssecureapps) to allow the attempts or +use another ESP to send email by replacing 'smtp.gmail.com' above with the address of your provider. Mailer Testing -------------- diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md index b99113ed3e..7f88c13dc0 100644 --- a/guides/source/active_record_validations.md +++ b/guides/source/active_record_validations.md @@ -242,7 +242,7 @@ end >> person = Person.new >> person.valid? ->> person.errors.details[:name] #=> [{error: :blank}] +>> person.errors.details[:name] # => [{error: :blank}] ``` Using `details` with custom validators is covered in the [Working with @@ -273,9 +273,13 @@ available helpers. This method validates that a checkbox on the user interface was checked when a form was submitted. This is typically used when the user needs to agree to your application's terms of service, confirm that some text is read, or any similar -concept. This validation is very specific to web applications and this -'acceptance' does not need to be recorded anywhere in your database (if you -don't have a field for it, the helper will just create a virtual attribute). +concept. + +This validation is very specific to web applications and this +'acceptance' does not need to be recorded anywhere in your database. If you +don't have a field for it, the helper will just create a virtual attribute. If +the field does exist in your database, the `accept` option must be set to +`true` or else the validation will not run. ```ruby class Person < ActiveRecord::Base @@ -636,7 +640,7 @@ class Holiday < ActiveRecord::Base message: "should happen once per year" } end ``` -Should you wish to create a database constraint to prevent possible violations of a uniqueness validation using the `:scope` option, you must create a unique index on both columns in your database. See [the MySQL manual](http://dev.mysql.com/doc/refman/5.6/en/multiple-column-indexes.html) for more details about multiple column indexes or [the PostgreSQL manual](http://www.postgresql.org/docs/current/static/ddl-constraints.html) for examples of unique constraints that refer to a group of columns. +Should you wish to create a database constraint to prevent possible violations of a uniqueness validation using the `:scope` option, you must create a unique index on both columns in your database. See [the MySQL manual](http://dev.mysql.com/doc/refman/5.7/en/multiple-column-indexes.html) for more details about multiple column indexes or [the PostgreSQL manual](http://www.postgresql.org/docs/current/static/ddl-constraints.html) for examples of unique constraints that refer to a group of columns. There is also a `:case_sensitive` option that you can use to define whether the uniqueness constraint will be case sensitive or not. This option defaults to diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md index 01bf928407..367a1bf7c0 100644 --- a/guides/source/active_support_core_extensions.md +++ b/guides/source/active_support_core_extensions.md @@ -1865,15 +1865,15 @@ The methods `to_date`, `to_time`, and `to_datetime` are basically convenience wr ```ruby "2010-07-27".to_date # => Tue, 27 Jul 2010 -"2010-07-27 23:37:00".to_time # => Tue Jul 27 23:37:00 UTC 2010 +"2010-07-27 23:37:00".to_time # => 2010-07-27 23:37:00 +0200 "2010-07-27 23:37:00".to_datetime # => Tue, 27 Jul 2010 23:37:00 +0000 ``` `to_time` receives an optional argument `:utc` or `:local`, to indicate which time zone you want the time in: ```ruby -"2010-07-27 23:42:00".to_time(:utc) # => Tue Jul 27 23:42:00 UTC 2010 -"2010-07-27 23:42:00".to_time(:local) # => Tue Jul 27 23:42:00 +0200 2010 +"2010-07-27 23:42:00".to_time(:utc) # => 2010-07-27 23:42:00 UTC +"2010-07-27 23:42:00".to_time(:local) # => 2010-07-27 23:42:00 +0200 ``` Default is `:utc`. diff --git a/guides/source/api_app.md b/guides/source/api_app.md index 28727a51bd..feaaff166a 100644 --- a/guides/source/api_app.md +++ b/guides/source/api_app.md @@ -194,7 +194,6 @@ An API application comes with the following middlewares by default: - `ActionDispatch::RemoteIp` - `ActionDispatch::Reloader` - `ActionDispatch::Callbacks` -- `ActionDispatch::ParamsParser` - `Rack::Head` - `Rack::ConditionalGet` - `Rack::ETag` @@ -292,9 +291,9 @@ instructions in the `Rack::Sendfile` documentation. NOTE: The `Rack::Sendfile` middleware is always outside of the `Rack::Lock` mutex, even in single-threaded applications. -### Using ActionDispatch::ParamsParser +### Using ActionDispatch::Request -`ActionDispatch::ParamsParser` will take parameters from the client in the JSON +`ActionDispatch::Request#params` will take parameters from the client in the JSON format and make them available in your controller inside `params`. To use this, your client will need to make a request with JSON-encoded parameters @@ -313,7 +312,7 @@ jQuery.ajax({ }); ``` -`ActionDispatch::ParamsParser` will see the `Content-Type` and your parameters +`ActionDispatch::Request` will see the `Content-Type` and your parameters will be: ```ruby diff --git a/guides/source/api_documentation_guidelines.md b/guides/source/api_documentation_guidelines.md index a4feff798d..526bf768cc 100644 --- a/guides/source/api_documentation_guidelines.md +++ b/guides/source/api_documentation_guidelines.md @@ -239,7 +239,7 @@ You can quickly test the RDoc output with the following command: ``` $ echo "+:to_param+" | rdoc --pipe -#=> <p><code>:to_param</code></p> +# => <p><code>:to_param</code></p> ``` ### Regular Font diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md index 1191f5edfe..60790b33a4 100644 --- a/guides/source/association_basics.md +++ b/guides/source/association_basics.md @@ -768,7 +768,7 @@ The `belongs_to` association creates a one-to-one match with another model. In d When you declare a `belongs_to` association, the declaring class automatically gains five methods related to the association: -* `association(force_reload = false)` +* `association` * `association=(associate)` * `build_association(attributes = {})` * `create_association(attributes = {})` @@ -794,7 +794,7 @@ create_customer! NOTE: When initializing a new `has_one` or `belongs_to` association you must use the `build_` prefix to build the association, rather than the `association.build` method that would be used for `has_many` or `has_and_belongs_to_many` associations. To create one, use the `create_` prefix. -##### `association(force_reload = false)` +##### `association` The `association` method returns the associated object, if any. If no associated object is found, it returns `nil`. @@ -802,7 +802,11 @@ The `association` method returns the associated object, if any. If no associated @customer = @order.customer ``` -If the associated object has already been retrieved from the database for this object, the cached version will be returned. To override this behavior (and force a database read), pass `true` as the `force_reload` argument. +If the associated object has already been retrieved from the database for this object, the cached version will be returned. To override this behavior (and force a database read), call `#reload` on the parent object. + +```ruby +@customer = @order.reload.customer +``` ##### `association=(associate)` @@ -1113,7 +1117,7 @@ The `has_one` association creates a one-to-one match with another model. In data When you declare a `has_one` association, the declaring class automatically gains five methods related to the association: -* `association(force_reload = false)` +* `association` * `association=(associate)` * `build_association(attributes = {})` * `create_association(attributes = {})` @@ -1139,7 +1143,7 @@ create_account! NOTE: When initializing a new `has_one` or `belongs_to` association you must use the `build_` prefix to build the association, rather than the `association.build` method that would be used for `has_many` or `has_and_belongs_to_many` associations. To create one, use the `create_` prefix. -##### `association(force_reload = false)` +##### `association` The `association` method returns the associated object, if any. If no associated object is found, it returns `nil`. @@ -1147,7 +1151,11 @@ The `association` method returns the associated object, if any. If no associated @account = @supplier.account ``` -If the associated object has already been retrieved from the database for this object, the cached version will be returned. To override this behavior (and force a database read), pass `true` as the `force_reload` argument. +If the associated object has already been retrieved from the database for this object, the cached version will be returned. To override this behavior (and force a database read), call `#reload` on the parent object. + +```ruby +@account = @supplier.reload.account +``` ##### `association=(associate)` @@ -1380,7 +1388,7 @@ The `has_many` association creates a one-to-many relationship with another model When you declare a `has_many` association, the declaring class automatically gains 16 methods related to the association: -* `collection(force_reload = false)` +* `collection` * `collection<<(object, ...)` * `collection.delete(object, ...)` * `collection.destroy(object, ...)` @@ -1408,7 +1416,7 @@ end Each instance of the `Customer` model will have these methods: ```ruby -orders(force_reload = false) +orders orders<<(object, ...) orders.delete(object, ...) orders.destroy(object, ...) @@ -1426,7 +1434,7 @@ orders.create(attributes = {}) orders.create!(attributes = {}) ``` -##### `collection(force_reload = false)` +##### `collection` The `collection` method returns an array of all of the associated objects. If there are no associated objects, it returns an empty array. @@ -1892,7 +1900,7 @@ The `has_and_belongs_to_many` association creates a many-to-many relationship wi When you declare a `has_and_belongs_to_many` association, the declaring class automatically gains 16 methods related to the association: -* `collection(force_reload = false)` +* `collection` * `collection<<(object, ...)` * `collection.delete(object, ...)` * `collection.destroy(object, ...)` @@ -1920,7 +1928,7 @@ end Each instance of the `Part` model will have these methods: ```ruby -assemblies(force_reload = false) +assemblies assemblies<<(object, ...) assemblies.delete(object, ...) assemblies.destroy(object, ...) @@ -1945,7 +1953,7 @@ If the join table for a `has_and_belongs_to_many` association has additional col WARNING: The use of extra attributes on the join table in a `has_and_belongs_to_many` association is deprecated. If you require this sort of complex behavior on the table that joins two models in a many-to-many relationship, you should use a `has_many :through` association instead of `has_and_belongs_to_many`. -##### `collection(force_reload = false)` +##### `collection` The `collection` method returns an array of all of the associated objects. If there are no associated objects, it returns an empty array. diff --git a/guides/source/command_line.md b/guides/source/command_line.md index cd265331d6..e85f9fc9c6 100644 --- a/guides/source/command_line.md +++ b/guides/source/command_line.md @@ -412,7 +412,7 @@ Ruby version 2.2.2 (x86_64-linux) RubyGems version 2.4.6 Rack version 1.6 JavaScript Runtime Node.js (V8) -Middleware Rack::Sendfile, ActionDispatch::Static, Rack::Lock, #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007ffd131a7c88>, Rack::Runtime, Rack::MethodOverride, ActionDispatch::RequestId, Rails::Rack::Logger, ActionDispatch::ShowExceptions, ActionDispatch::DebugExceptions, ActionDispatch::RemoteIp, ActionDispatch::Reloader, ActionDispatch::Callbacks, ActiveRecord::Migration::CheckPending, ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, ActionDispatch::ParamsParser, Rack::Head, Rack::ConditionalGet, Rack::ETag +Middleware Rack::Sendfile, ActionDispatch::Static, Rack::Lock, #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007ffd131a7c88>, Rack::Runtime, Rack::MethodOverride, ActionDispatch::RequestId, Rails::Rack::Logger, ActionDispatch::ShowExceptions, ActionDispatch::DebugExceptions, ActionDispatch::RemoteIp, ActionDispatch::Reloader, ActionDispatch::Callbacks, ActiveRecord::Migration::CheckPending, ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, Rack::Head, Rack::ConditionalGet, Rack::ETag Application root /home/foobar/commandsapp Environment development Database adapter sqlite3 diff --git a/guides/source/configuring.md b/guides/source/configuring.md index 5e72b96787..0dd99cf8e9 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -214,7 +214,6 @@ Every Rails application comes with a standard set of middleware which it uses in * `ActionDispatch::Cookies` sets cookies for the request. * `ActionDispatch::Session::CookieStore` is responsible for storing the session in cookies. An alternate middleware can be used for this by changing the `config.action_controller.session_store` to an alternate value. Additionally, options passed to this can be configured by using `config.action_controller.session_options`. * `ActionDispatch::Flash` sets up the `flash` keys. Only available if `config.action_controller.session_store` is set to a value. -* `ActionDispatch::ParamsParser` parses out parameters from the request into `params`. * `Rack::MethodOverride` allows the method to be overridden if `params[:_method]` is set. This is the middleware which supports the PATCH, PUT, and DELETE HTTP method types. * `Rack::Head` converts HEAD requests to GET requests and serves them as so. diff --git a/guides/source/i18n.md b/guides/source/i18n.md index 272a0e3623..987320a0f8 100644 --- a/guides/source/i18n.md +++ b/guides/source/i18n.md @@ -51,7 +51,7 @@ Thus, the Ruby I18n gem is split into two parts: As a user you should always only access the public methods on the I18n module, but it is useful to know about the capabilities of the backend. -NOTE: It is possible (or even desirable) to swap the shipped Simple backend with a more powerful one, which would store translation data in a relational database, GetText dictionary, or similar. See section [Using different backends](#using-different-backends) below. +NOTE: It is possible to swap the shipped Simple backend with a more powerful one, which would store translation data in a relational database, GetText dictionary, or similar. See section [Using different backends](#using-different-backends) below. ### The Public I18n API diff --git a/guides/source/kindle/layout.html.erb b/guides/source/kindle/layout.html.erb index f0a286210b..fd8746776b 100644 --- a/guides/source/kindle/layout.html.erb +++ b/guides/source/kindle/layout.html.erb @@ -14,12 +14,12 @@ <% if content_for? :header_section %> <%= yield :header_section %> - <div class="pagebreak"> + <div class="pagebreak"></div> <% end %> <% if content_for? :index_section %> <%= yield :index_section %> - <div class="pagebreak"> + <div class="pagebreak"></div> <% end %> <%= yield.html_safe %> diff --git a/guides/source/kindle/toc.ncx.erb b/guides/source/kindle/toc.ncx.erb index 2c6d8e3bdf..5094fea4ca 100644 --- a/guides/source/kindle/toc.ncx.erb +++ b/guides/source/kindle/toc.ncx.erb @@ -32,12 +32,12 @@ </navPoint> <navPoint class="article" id="credits" playOrder="3"> <navLabel><text>Credits</text></navLabel> - <content src="credits.html"> + <content src="credits.html"/> </navPoint> <navPoint class="article" id="copyright" playOrder="4"> <navLabel><text>Copyright & License</text></navLabel> - <content src="copyright.html"> - </navPoint> + <content src="copyright.html"/> + </navPoint> </navPoint> <% play_order = 4 %> @@ -47,7 +47,7 @@ <text><%= section['name'] %></text> </navLabel> <content src="<%=section['documents'].first['url'] %>"/> - + <% section['documents'].each_with_index do |document, document_no| %> <navPoint class="article" id="_<%=section_no+1%>.<%=document_no+1%>" playOrder="<%=play_order +=1 %>"> <navLabel> diff --git a/guides/source/rails_application_templates.md b/guides/source/rails_application_templates.md index b7364536c3..edd54826cf 100644 --- a/guides/source/rails_application_templates.md +++ b/guides/source/rails_application_templates.md @@ -78,7 +78,7 @@ gem_group :development, :test do end ``` -### add_source(source, options = {}) +### add_source(source, options={}, &block) Adds the given source to the generated application's `Gemfile`. @@ -88,6 +88,14 @@ For example, if you need to source a gem from `"http://code.whytheluckystiff.net add_source "http://code.whytheluckystiff.net" ``` +If block is given, gem entries in block are wrapped into the source group. + +```ruby +add_source "http://gems.github.com/" do + gem "rspec-rails" +end +``` + ### environment/application(data=nil, options={}, &block) Adds a line inside the `Application` class for `config/application.rb`. diff --git a/guides/source/rails_on_rack.md b/guides/source/rails_on_rack.md index 1e2fe94010..0db90fedb3 100644 --- a/guides/source/rails_on_rack.md +++ b/guides/source/rails_on_rack.md @@ -121,7 +121,6 @@ use ActiveRecord::QueryCache use ActionDispatch::Cookies use ActionDispatch::Session::CookieStore use ActionDispatch::Flash -use ActionDispatch::ParamsParser use Rack::Head use Rack::ConditionalGet use Rack::ETag @@ -284,10 +283,6 @@ Much of Action Controller's functionality is implemented as Middlewares. The fol * Sets up the flash keys. Only available if `config.action_controller.session_store` is set to a value. -**`ActionDispatch::ParamsParser`** - -* Parses out parameters from the request into `params`. - **`Rack::Head`** * Converts HEAD requests to `GET` requests and serves them as so. diff --git a/guides/source/security.md b/guides/source/security.md index 93c270064a..5a6ac9446a 100644 --- a/guides/source/security.md +++ b/guides/source/security.md @@ -245,7 +245,9 @@ Or the attacker places the code into the onmouseover event handler of an image: <img src="http://www.harmless.com/img" width="400" height="400" onmouseover="..." /> ``` -There are many other possibilities, like using a `<script>` tag to make a cross-site request to a URL with a JSONP or JavaScript response. The response is executable code that the attacker can find a way to run, possibly extracting sensitive data. To protect against this data leakage, we disallow cross-site `<script>` tags. Only Ajax requests may have JavaScript responses since `XMLHttpRequest` is subject to the browser Same-Origin policy - meaning only your site can initiate the request. +There are many other possibilities, like using a `<script>` tag to make a cross-site request to a URL with a JSONP or JavaScript response. The response is executable code that the attacker can find a way to run, possibly extracting sensitive data. To protect against this data leakage, we must disallow cross-site `<script>` tags. Ajax requests, however, obey the browser's same-origin policy (only your own site is allowed to initiate `XmlHttpRequest`) so we can safely allow them to return JavaScript responses. + +Note: We can't distinguish a `<script>` tag's origin—whether it's a tag on your own site or on some other malicious site—so we must block all `<script>` across the board, even if it's actually a safe same-origin script served from your own site. In these cases, explicitly skip CSRF protection on actions that serve JavaScript meant for a `<script>` tag. To protect against all other forged requests, we introduce a _required security token_ that our site knows but other sites don't know. We include the security token in requests and verify it on the server. This is a one-liner in your application controller, and is the default for newly created rails applications: diff --git a/guides/source/testing.md b/guides/source/testing.md index aa3497fa13..435de30acc 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -302,7 +302,7 @@ specify to make your test failure messages clearer. It's not required. The above are a subset of assertions that minitest supports. For an exhaustive & more up-to-date list, please check [Minitest API documentation](http://docs.seattlerb.org/minitest/), specifically -[`Minitest::Assertions`](http://docs.seattlerb.org/minitest/Minitest/Assertions.html) +[`Minitest::Assertions`](http://docs.seattlerb.org/minitest/Minitest/Assertions.html). Because of the modular nature of the testing framework, it is possible to create your own assertions. In fact, that's exactly what Rails does. It includes some specialized assertions to make your life easier. @@ -478,7 +478,7 @@ default. Loading involves three steps: 2. Load the fixture data into the table 3. Dump the fixture data into a method in case you want to access it directly -TIP: In order to remove existing data from the database, Rails tries to disable referential integrity triggers (like foreign keys and check constraints). If you are getting annoying permission errors on running tests, make sure the database user has privilege to disable these triggers in testing environment. (In PostgreSQL, only superusers can disable all triggers. Read more about PostgreSQL permissions [here](http://blog.endpoint.com/2012/10/postgres-system-triggers-error.html)) +TIP: In order to remove existing data from the database, Rails tries to disable referential integrity triggers (like foreign keys and check constraints). If you are getting annoying permission errors on running tests, make sure the database user has privilege to disable these triggers in testing environment. (In PostgreSQL, only superusers can disable all triggers. Read more about PostgreSQL permissions [here](http://blog.endpoint.com/2012/10/postgres-system-triggers-error.html)). #### Fixtures are Active Record objects @@ -750,9 +750,9 @@ end After a request has been made and processed, you will have 3 Hash objects ready for use: -* `cookies` - Any cookies that are set. -* `flash` - Any objects living in the flash. -* `session` - Any object living in session variables. +* `cookies` - Any cookies that are set +* `flash` - Any objects living in the flash +* `session` - Any object living in session variables As is the case with normal Hash objects, you can access the values by referencing the keys by string. You can also reference them by symbol name. For example: diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md index 30c0fcb294..52464a1c51 100644 --- a/guides/source/upgrading_ruby_on_rails.md +++ b/guides/source/upgrading_ruby_on_rails.md @@ -314,11 +314,11 @@ Upgrading from Rails 4.0 to Rails 4.1 ### CSRF protection from remote `<script>` tags -Or, "whaaat my tests are failing!!!?" +Or, "whaaat my tests are failing!!!?" or "my `<script>` widget is busted!!" Cross-site request forgery (CSRF) protection now covers GET requests with -JavaScript responses, too. This prevents a third-party site from referencing -your JavaScript URL and attempting to run it to extract sensitive data. +JavaScript responses, too. This prevents a third-party site from remotely +referencing your JavaScript with a `<script>` tag to extract sensitive data. This means that your functional and integration tests that use @@ -334,8 +334,9 @@ xhr :get, :index, format: :js to explicitly test an `XmlHttpRequest`. -If you really mean to load JavaScript from remote `<script>` tags, skip CSRF -protection on that action. +Note: Your own `<script>` tags are treated as cross-origin and blocked by +default, too. If you really mean to load JavaScript from `<script>` tags, +you must now explicitly skip CSRF protection on those actions. ### Spring diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index faf6846e4b..f007d11c72 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,8 @@ +* Fix displaying mailer previews on non local requests when config + `action_mailer.show_previews` is set + + *Wojciech Wnętrzak* + * `rails server` will now honour the `PORT` environment variable *David Cornu* diff --git a/railties/lib/rails/application/default_middleware_stack.rb b/railties/lib/rails/application/default_middleware_stack.rb index 88eade5c5a..21062f3a53 100644 --- a/railties/lib/rails/application/default_middleware_stack.rb +++ b/railties/lib/rails/application/default_middleware_stack.rb @@ -72,7 +72,6 @@ module Rails middleware.use ::ActionDispatch::Flash end - middleware.use ::ActionDispatch::ParamsParser middleware.use ::Rack::Head middleware.use ::Rack::ConditionalGet middleware.use ::Rack::ETag, "no-cache" diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 38074e80e0..0f44f4694e 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -205,24 +205,21 @@ module Rails end def rails_gemfile_entry - if options.dev? - [ - GemfileEntry.path('rails', Rails::Generators::RAILS_DEV_PATH), + dev_edge_common = [ GemfileEntry.github('sprockets-rails', 'rails/sprockets-rails'), GemfileEntry.github('sprockets', 'rails/sprockets'), GemfileEntry.github('sass-rails', 'rails/sass-rails'), GemfileEntry.github('arel', 'rails/arel'), GemfileEntry.github('rack', 'rack/rack') ] + if options.dev? + [ + GemfileEntry.path('rails', Rails::Generators::RAILS_DEV_PATH) + ] + dev_edge_common elsif options.edge? [ - GemfileEntry.github('rails', 'rails/rails'), - GemfileEntry.github('sprockets-rails', 'rails/sprockets-rails'), - GemfileEntry.github('sprockets', 'rails/sprockets'), - GemfileEntry.github('sass-rails', 'rails/sass-rails'), - GemfileEntry.github('arel', 'rails/arel'), - GemfileEntry.github('rack', 'rack/rack') - ] + GemfileEntry.github('rails', 'rails/rails') + ] + dev_edge_common else [GemfileEntry.version('rails', Rails::VERSION::STRING, diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index 6a1c2faaab..975be07622 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -50,8 +50,6 @@ group :development do <% end -%> end <% end -%> -<% if RUBY_PLATFORM.match(/bccwin|cygwin|emx|mingw|mswin|wince|java/) -%> # Windows does not include zoneinfo files, so bundle the tzinfo-data gem gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] -<% end -%> diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml index f5b62e8fb3..5ca549a8c8 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/jdbcmysql.yml @@ -7,7 +7,7 @@ # gem 'activerecord-jdbcmysql-adapter' # # And be sure to use new-style password hashing: -# http://dev.mysql.com/doc/refman/5.6/en/old-client.html +# http://dev.mysql.com/doc/refman/5.7/en/old-client.html # default: &default adapter: mysql diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml index b0767bd93a..119c2fe2c3 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/mysql.yml @@ -7,7 +7,7 @@ # gem 'mysql2' # # And be sure to use new-style password hashing: -# http://dev.mysql.com/doc/refman/5.6/en/old-client.html +# http://dev.mysql.com/doc/refman/5.7/en/old-client.html # default: &default adapter: mysql2 diff --git a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb index 2c3b04043f..f73e9a96ba 100644 --- a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb +++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb @@ -1,5 +1,5 @@ <% if namespaced? -%> -require_dependency "<%= namespaced_file_path %>/application_controller" +require_dependency "<%= namespaced_path %>/application_controller" <% end -%> <% module_namespacing do -%> diff --git a/railties/lib/rails/mailers_controller.rb b/railties/lib/rails/mailers_controller.rb index 78a857e0f1..ed0ea9dd4d 100644 --- a/railties/lib/rails/mailers_controller.rb +++ b/railties/lib/rails/mailers_controller.rb @@ -3,7 +3,7 @@ require 'rails/application_controller' class Rails::MailersController < Rails::ApplicationController # :nodoc: prepend_view_path ActionDispatch::DebugExceptions::RESCUES_TEMPLATE_PATH - before_action :require_local! + before_action :require_local!, unless: :show_previews? before_action :find_preview, only: :preview def index @@ -41,6 +41,10 @@ class Rails::MailersController < Rails::ApplicationController # :nodoc: end protected + def show_previews? + ActionMailer::Base.show_previews + end + def find_preview candidates = [] params[:path].to_s.scan(%r{/|$}){ candidates << $` } diff --git a/railties/test/application/mailer_previews_test.rb b/railties/test/application/mailer_previews_test.rb index e462d2c15e..643d876a26 100644 --- a/railties/test/application/mailer_previews_test.rb +++ b/railties/test/application/mailer_previews_test.rb @@ -31,7 +31,7 @@ module ApplicationTests test "/rails/mailers is accessible with correct configuraiton" do add_to_config "config.action_mailer.show_previews = true" app("production") - get "/rails/mailers" + get "/rails/mailers", {}, {"REMOTE_ADDR" => "4.2.42.42"} assert_equal 200, last_response.status end diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb index d298e8d632..138c63266e 100644 --- a/railties/test/application/middleware_test.rb +++ b/railties/test/application/middleware_test.rb @@ -43,7 +43,6 @@ module ApplicationTests "ActionDispatch::Cookies", "ActionDispatch::Session::CookieStore", "ActionDispatch::Flash", - "ActionDispatch::ParamsParser", "Rack::Head", "Rack::ConditionalGet", "Rack::ETag" @@ -70,7 +69,6 @@ module ApplicationTests "ActionDispatch::Callbacks", "ActiveRecord::ConnectionAdapters::ConnectionManagement", "ActiveRecord::QueryCache", - "ActionDispatch::ParamsParser", "Rack::Head", "Rack::ConditionalGet", "Rack::ETag" diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb index a040dd4cf6..0da0928b48 100644 --- a/railties/test/application/rake_test.rb +++ b/railties/test/application/rake_test.rb @@ -1,4 +1,3 @@ -# coding:utf-8 require "isolation/abstract_unit" require "active_support/core_ext/string/strip" diff --git a/railties/test/generators/namespaced_generators_test.rb b/railties/test/generators/namespaced_generators_test.rb index e839b67960..c4ee6602c5 100644 --- a/railties/test/generators/namespaced_generators_test.rb +++ b/railties/test/generators/namespaced_generators_test.rb @@ -281,6 +281,7 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase # Controller assert_file "app/controllers/test_app/admin/roles_controller.rb" do |content| assert_match(/module TestApp\n class Admin::RolesController < ApplicationController/, content) + assert_match(%r(require_dependency "test_app/application_controller"), content) end assert_file "test/controllers/test_app/admin/roles_controller_test.rb", |