diff options
572 files changed, 6171 insertions, 4114 deletions
diff --git a/.gitignore b/.gitignore index 9268977c2f..4961ad588f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@  .Gemfile  .ruby-version +.byebug_history  debug.log  pkg  /.bundle diff --git a/.rubocop.yml b/.rubocop.yml index 629f5d86ea..a9ced4c0a5 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -113,7 +113,7 @@ Style/UnneededPercentQ:  # assignments, where it should be aligned with the LHS.  Lint/EndAlignment:    Enabled: true -  AlignWith: variable +  EnforcedStyleAlignWith: variable  # Use my_method(my_arg) not my_method( my_arg ) or my_method my_arg.  Lint/RequireParentheses: diff --git a/.travis.yml b/.travis.yml index 00f10d74e3..eafa06e44f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,6 +17,7 @@ addons:  bundler_args: --without test --jobs 3 --retry 3  before_install:    - "rm ${BUNDLE_GEMFILE}.lock" +  - "gem update --system"    - "gem update bundler"    - "[ -f /tmp/beanstalkd-1.10/Makefile ] || (curl -L https://github.com/kr/beanstalkd/archive/v1.10.tar.gz | tar xz -C /tmp)"    - "pushd /tmp/beanstalkd-1.10 && make && (./beanstalkd &); popd" @@ -46,7 +47,7 @@ env:  rvm:    - 2.2.6    - 2.3.3 -  - 2.4.0-rc1 +  - 2.4.0    - ruby-head  matrix: @@ -63,6 +64,12 @@ matrix:          - memcached          - redis          - rabbitmq +    - rvm: 2.4.0 +      env: "GEM=aj:integration" +      services: +        - memcached +        - redis +        - rabbitmq      - rvm: ruby-head        env: "GEM=aj:integration"        services: @@ -74,17 +81,20 @@ matrix:          - "GEM=ar:mysql2 MYSQL=mariadb"        addons:          mariadb: 10.0 -    - rvm: jruby-9.1.5.0 +    - rvm: 2.4.0 +      env: +        - "GEM=ar:sqlite3_mem" +    - rvm: jruby-9.1.7.0        jdk: oraclejdk8        env:          - "GEM=ap" -    - rvm: jruby-9.1.5.0 +    - rvm: jruby-9.1.7.0        jdk: oraclejdk8        env:          - "GEM=am,aj"    allow_failures:      - rvm: ruby-head -    - rvm: jruby-9.1.5.0 +    - rvm: jruby-9.1.7.0      - env: "GEM=ac:integration"    fast_finish: true @@ -7,6 +7,8 @@ end  gemspec +gem "arel", github: "rails/arel" +  # We need a newish Rake since Active Job sets its test tasks' descriptions.  gem "rake", ">= 11.1" @@ -29,19 +31,16 @@ gem "bcrypt", "~> 3.1.11", require: false  # sprockets.  gem "uglifier", ">= 1.3.0", require: false -# Track stable branch of sass because it doesn't have circular require warnings. -gem "sass", github: "sass/sass", branch: "stable", require: false -  # FIXME: Remove this fork after https://github.com/nex3/rb-inotify/pull/49 is fixed.  gem "rb-inotify", github: "matthewd/rb-inotify", branch: "close-handling", require: false  # Explicitly avoid 1.x that doesn't support Ruby 2.4+  gem "json", ">= 2.0.0" -gem "rubocop", require: false +gem "rubocop", ">= 0.47", require: false  group :doc do -  gem "sdoc", "1.0.0.beta2" +  gem "sdoc", "1.0.0.rc1"    gem "redcarpet", "~> 3.2.3", platforms: :ruby    gem "w3c_validators"    gem "kindlerb", "~> 1.2.0" @@ -52,13 +51,16 @@ gem "dalli", ">= 2.2.1"  gem "listen", ">= 3.0.5", "< 3.2", require: false  gem "libxml-ruby", platforms: :ruby +# Action View. For testing Erubis handler deprecation. +gem "erubis", "~> 2.7.0", require: false +  # Active Job.  group :job do    gem "resque", github: "resque/resque", require: false    gem "resque-scheduler", require: false    gem "sidekiq", require: false    gem "sucker_punch", require: false -  gem "delayed_job", require: false, github: "collectiveidea/delayed_job" +  gem "delayed_job", require: false    gem "queue_classic", github: "QueueClassic/queue_classic", branch: "master", require: false, platforms: :ruby    gem "sneakers", require: false    gem "que", require: false @@ -66,7 +68,7 @@ group :job do    #TODO: add qu after it support Rails 5.1    # gem 'qu-rails', github: "bkeepers/qu", branch: "master", require: false    gem "qu-redis", require: false -  gem "delayed_job_active_record", require: false, github: "collectiveidea/delayed_job_active_record" +  gem "delayed_job_active_record", require: false    gem "sequel", require: false  end diff --git a/Gemfile.lock b/Gemfile.lock index dc1f57a77d..049d7320b8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -7,21 +7,6 @@ GIT        pg (>= 0.17, < 0.20)  GIT -  remote: https://github.com/collectiveidea/delayed_job.git -  revision: e3772d4f0c8470d0fcba00c86ca3bc4f5e876830 -  specs: -    delayed_job (4.1.2) -      activesupport (>= 3.0, < 5.1) - -GIT -  remote: https://github.com/collectiveidea/delayed_job_active_record.git -  revision: 36f434c4fd660e8f11ce932be117e9c71dde7212 -  specs: -    delayed_job_active_record (4.1.1) -      activerecord (>= 3.0, < 5.1) -      delayed_job (>= 3.0, < 5) - -GIT    remote: https://github.com/matthewd/rb-inotify.git    revision: 90553518d1fb79aedc98a3036c59bd2b6731ac40    branch: close-handling @@ -39,8 +24,14 @@ GIT        websocket  GIT +  remote: https://github.com/rails/arel.git +  revision: ab109d3bf1c773da5e78ddc93bb6b55aebbb1c2a +  specs: +    arel (8.0.0) + +GIT    remote: https://github.com/resque/resque.git -  revision: 20d885065ac19e7f7d7a982f4ed1296083db0300 +  revision: 835a4a7e61a2e33832dbf11975ecfab54af293c6    specs:      resque (1.27.0)        mono_logger (~> 1.0) @@ -49,19 +40,12 @@ GIT        sinatra (>= 0.9.2)        vegas (~> 0.1.2) -GIT -  remote: https://github.com/sass/sass.git -  revision: 7716e67f3507c6f65878c69aa49ec358ebf675c7 -  branch: stable -  specs: -    sass (3.4.22) -  PATH    remote: .    specs:      actioncable (5.1.0.alpha)        actionpack (= 5.1.0.alpha) -      nio4r (~> 1.2) +      nio4r (~> 2.0)        websocket-driver (~> 0.6.1)      actionmailer (5.1.0.alpha)        actionpack (= 5.1.0.alpha) @@ -79,9 +63,9 @@ PATH      actionview (5.1.0.alpha)        activesupport (= 5.1.0.alpha)        builder (~> 3.1) -      erubis (~> 2.7.0) +      erubi (~> 1.4)        rails-dom-testing (~> 2.0) -      rails-html-sanitizer (~> 1.0, >= 1.0.2) +      rails-html-sanitizer (~> 1.0, >= 1.0.3)      activejob (5.1.0.alpha)        activesupport (= 5.1.0.alpha)        globalid (>= 0.3.6) @@ -90,7 +74,7 @@ PATH      activerecord (5.1.0.alpha)        activemodel (= 5.1.0.alpha)        activesupport (= 5.1.0.alpha) -      arel (~> 7.0) +      arel (~> 8.0)      activesupport (5.1.0.alpha)        concurrent-ruby (~> 1.0, >= 1.0.2)        i18n (~> 0.7) @@ -120,8 +104,7 @@ GEM    specs:      addressable (2.5.0)        public_suffix (~> 2.0, >= 2.0.2) -    amq-protocol (2.0.1) -    arel (7.1.2) +    amq-protocol (2.1.0)      ast (2.3.0)      backburner (1.3.1)        beaneater (~> 1.0) @@ -132,7 +115,7 @@ GEM      bcrypt (3.1.11-x86-mingw32)      beaneater (1.0.0)      benchmark-ips (2.7.2) -    blade (0.6.1) +    blade (0.7.0)        activesupport (>= 3.0.0)        blade-qunit_adapter (~> 2.0.1)        coffee-script @@ -141,17 +124,16 @@ GEM        eventmachine        faye        sprockets (>= 3.0) -      sprockets-export (~> 0.9.1)        thin (>= 1.6.0)        thor (~> 0.19.1)        useragent (~> 0.16.7)      blade-qunit_adapter (2.0.1) -    blade-sauce_labs_plugin (0.6.1) +    blade-sauce_labs_plugin (0.6.2)        childprocess        faraday        selenium-webdriver -    builder (3.2.2) -    bunny (2.2.2) +    builder (3.2.3) +    bunny (2.6.2)        amq-protocol (>= 2.0.1)      byebug (9.0.6)      childprocess (0.5.9) @@ -163,13 +145,18 @@ GEM        coffee-script-source        execjs      coffee-script-source (1.12.2) -    concurrent-ruby (1.0.3) -    connection_pool (2.2.0) +    concurrent-ruby (1.0.4) +    connection_pool (2.2.1)      cookiejar (0.3.3)      curses (1.0.2)      daemons (1.2.4)      dalli (2.7.6)      dante (0.2.0) +    delayed_job (4.1.2) +      activesupport (>= 3.0, < 5.1) +    delayed_job_active_record (4.1.1) +      activerecord (>= 3.0, < 5.1) +      delayed_job (>= 3.0, < 5)      em-hiredis (0.3.1)        eventmachine (~> 1.0)        hiredis (~> 0.6.0) @@ -181,13 +168,14 @@ GEM        http_parser.rb (>= 0.6.0)      em-socksify (0.3.1)        eventmachine (>= 1.0.0.beta.4) +    erubi (1.4.0)      erubis (2.7.0)      event_emitter (0.2.5)      eventmachine (1.2.1)      eventmachine (1.2.1-x64-mingw32)      eventmachine (1.2.1-x86-mingw32)      execjs (2.7.0) -    faraday (0.10.0) +    faraday (0.11.0)        multipart-post (>= 1.2, < 3)      faye (1.2.3)        cookiejar (>= 0.3.0) @@ -200,19 +188,17 @@ GEM      faye-websocket (0.10.5)        eventmachine (>= 0.12.0)        websocket-driver (>= 0.5.1) -    ffi (1.9.14) -    ffi (1.9.14-x64-mingw32) -    ffi (1.9.14-x86-mingw32) +    ffi (1.9.17)      globalid (0.3.7)        activesupport (>= 4.1.0)      hiredis (0.6.1)      http_parser.rb (0.6.0)      i18n (0.7.0) -    jquery-rails (4.2.1) +    jquery-rails (4.2.2)        rails-dom-testing (>= 1, < 3)        railties (>= 4.2.0)        thor (>= 0.14, < 2.0) -    json (2.0.2) +    json (2.0.3)      kindlerb (1.2.0)        mustache        nokogiri @@ -241,22 +227,22 @@ GEM      mysql2 (0.4.5)      mysql2 (0.4.5-x64-mingw32)      mysql2 (0.4.5-x86-mingw32) -    nio4r (1.2.1) -    nokogiri (1.6.8.1) +    nio4r (2.0.0) +    nokogiri (1.7.0.1)        mini_portile2 (~> 2.1.0) -    nokogiri (1.6.8.1-x64-mingw32) +    nokogiri (1.7.0.1-x64-mingw32)        mini_portile2 (~> 2.1.0) -    nokogiri (1.6.8.1-x86-mingw32) +    nokogiri (1.7.0.1-x86-mingw32)        mini_portile2 (~> 2.1.0) -    parser (2.3.2.0) +    parser (2.3.3.1)        ast (~> 2.2)      pg (0.19.0)      pg (0.19.0-x64-mingw32)      pg (0.19.0-x86-mingw32)      powerpack (0.1.1) -    psych (2.1.1) -    public_suffix (2.0.4) -    puma (3.6.0) +    psych (2.2.2) +    public_suffix (2.0.5) +    puma (3.7.0)      qu (0.2.0)        multi_json      qu-redis (0.2.0) @@ -272,17 +258,17 @@ GEM        rack      rack-test (0.6.3)        rack (>= 1.0) -    rails-dom-testing (2.0.1) +    rails-dom-testing (2.0.2)        activesupport (>= 4.2.0, < 6.0) -      nokogiri (~> 1.6.0) +      nokogiri (~> 1.6)      rails-html-sanitizer (1.0.3)        loofah (~> 2.0) -    rainbow (2.1.0) +    rainbow (2.2.1)      rake (12.0.0) -    rb-fsevent (0.9.7) -    rdoc (5.0.0.beta2) +    rb-fsevent (0.9.8) +    rdoc (5.0.0)      redcarpet (3.2.3) -    redis (3.3.1) +    redis (3.3.2)      redis-namespace (1.5.2)        redis (~> 3.0, >= 3.0.4)      resque-scheduler (4.3.0) @@ -290,56 +276,57 @@ GEM        redis (~> 3.3)        resque (~> 1.26)        rufus-scheduler (~> 3.2) -    rubocop (0.45.0) -      parser (>= 2.3.1.1, < 3.0) +    rubocop (0.47.1) +      parser (>= 2.3.3.1, < 3.0)        powerpack (~> 0.1)        rainbow (>= 1.99.1, < 3.0)        ruby-progressbar (~> 1.7)        unicode-display_width (~> 1.0, >= 1.0.1)      ruby-progressbar (1.8.1) -    ruby_dep (1.4.0) +    ruby_dep (1.5.0)      rubyzip (1.2.0) -    rufus-scheduler (3.2.2) +    rufus-scheduler (3.3.2) +      tzinfo +    sass (3.4.23)      sass-rails (5.0.6)        railties (>= 4.0.0, < 6)        sass (~> 3.1)        sprockets (>= 2.8, < 4.0)        sprockets-rails (>= 2.0, < 4.0)        tilt (>= 1.1, < 3) -    sdoc (1.0.0.beta2) -      rdoc (= 5.0.0.beta2) -    selenium-webdriver (3.0.1) +    sdoc (1.0.0.rc1) +      rdoc (= 5.0.0) +    selenium-webdriver (3.0.5)        childprocess (~> 0.5)        rubyzip (~> 1.0)        websocket (~> 1.0) -    sequel (4.39.0) +    sequel (4.42.1)      serverengine (1.5.11)        sigdump (~> 0.2.2) -    sidekiq (4.2.2) +    sidekiq (4.2.9)        concurrent-ruby (~> 1.0)        connection_pool (~> 2.2, >= 2.2.0) -      rack-protection (~> 1.5) +      rack-protection (>= 1.5.0)        redis (~> 3.2, >= 3.2.1)      sigdump (0.2.4)      simple_uuid (0.4.0)      sinatra (1.0)        rack (>= 1.0) -    sneakers (2.3.5) -      bunny (~> 2.2.0) +    sneakers (2.4.0) +      bunny (~> 2.6)        serverengine (~> 1.5.11)        thor        thread (~> 0.1.7)      sprockets (3.7.1)        concurrent-ruby (~> 1.0)        rack (> 1, < 3) -    sprockets-export (0.9.1)      sprockets-rails (3.2.0)        actionpack (>= 4.0)        activesupport (>= 4.0)        sprockets (>= 3.0.0) -    sqlite3 (1.3.12) -    sqlite3 (1.3.12-x64-mingw32) -    sqlite3 (1.3.12-x86-mingw32) +    sqlite3 (1.3.13) +    sqlite3 (1.3.13-x64-mingw32) +    sqlite3 (1.3.13-x86-mingw32)      stackprof (0.2.10)      sucker_punch (2.0.2)        concurrent-ruby (~> 1.0.0) @@ -347,7 +334,7 @@ GEM        daemons (~> 1.0, >= 1.0.9)        eventmachine (~> 1.0, >= 1.0.4)        rack (>= 1, < 3) -    thor (0.19.1) +    thor (0.19.4)      thread (0.1.7)      thread_safe (0.3.5)      tilt (2.0.5) @@ -356,17 +343,17 @@ GEM      turbolinks-source (5.0.0)      tzinfo (1.2.2)        thread_safe (~> 0.1) -    tzinfo-data (1.2016.7) +    tzinfo-data (1.2016.10)        tzinfo (>= 1.0.0) -    uglifier (3.0.2) +    uglifier (3.0.4)        execjs (>= 0.3.0, < 3) -    unicode-display_width (1.1.1) +    unicode-display_width (1.1.3)      useragent (0.16.8)      vegas (0.1.11)        rack (>= 1.0.0) -    w3c_validators (1.2) -      json -      nokogiri +    w3c_validators (1.3.1) +      json (~> 2.0) +      nokogiri (~> 1.6)      wdm (0.1.1)      websocket (1.2.3)      websocket-driver (0.6.4) @@ -382,6 +369,7 @@ DEPENDENCIES    activerecord-jdbcmysql-adapter (>= 1.3.0)    activerecord-jdbcpostgresql-adapter (>= 1.3.0)    activerecord-jdbcsqlite3-adapter (>= 1.3.0) +  arel!    backburner    bcrypt (~> 3.1.11)    benchmark-ips @@ -390,9 +378,10 @@ DEPENDENCIES    byebug    coffee-rails    dalli (>= 2.2.1) -  delayed_job! -  delayed_job_active_record! +  delayed_job +  delayed_job_active_record    em-hiredis +  erubis (~> 2.7.0)    hiredis    jquery-rails    json (>= 2.0.0) @@ -418,10 +407,9 @@ DEPENDENCIES    redis    resque!    resque-scheduler -  rubocop -  sass! +  rubocop (>= 0.47)    sass-rails -  sdoc (= 1.0.0.beta2) +  sdoc (= 1.0.0.rc1)    sequel    sidekiq    sneakers @@ -436,4 +424,4 @@ DEPENDENCIES    websocket-client-simple!  BUNDLED WITH -   1.13.6 +   1.14.3 diff --git a/MIT-LICENSE b/MIT-LICENSE index 40235833ba..6b3cead1a7 100644 --- a/MIT-LICENSE +++ b/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2005-2016 David Heinemeier Hansson +Copyright (c) 2005-2017 David Heinemeier Hansson  Permission is hereby granted, free of charge, to any person obtaining  a copy of this software and associated documentation files (the diff --git a/actioncable/CHANGELOG.md b/actioncable/CHANGELOG.md index 2c84d3158f..7657a05077 100644 --- a/actioncable/CHANGELOG.md +++ b/actioncable/CHANGELOG.md @@ -1,3 +1,9 @@ +*   Redis subscription adapters now support `channel_prefix` option in `cable.yml` + +    Avoids channel name collisions when multiple apps use the same Redis server. + +    *Chad Ingram* +  *   Permit same-origin connections by default.      Added new option `config.action_cable.allow_same_origin_as_host = false` @@ -13,12 +19,12 @@      *Vladimir Dementyev* -*   Buffer now writes to websocket connections, to avoid blocking threads +*   Buffer now writes to WebSocket connections, to avoid blocking threads      that could be doing more useful things.      *Matthew Draper*, *Tinco Andringa* -*   Protect against concurrent writes to a websocket connection from +*   Protect against concurrent writes to a WebSocket connection from      multiple threads; the underlying OS write is not always threadsafe.      *Tinco Andringa* diff --git a/actioncable/MIT-LICENSE b/actioncable/MIT-LICENSE index 27a17cf41b..1a0e653b69 100644 --- a/actioncable/MIT-LICENSE +++ b/actioncable/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2015-2016 Basecamp, LLC +Copyright (c) 2015-2017 Basecamp, LLC  Permission is hereby granted, free of charge, to any person obtaining  a copy of this software and associated documentation files (the diff --git a/actioncable/README.md b/actioncable/README.md index cccb55a196..c55b7dc57b 100644 --- a/actioncable/README.md +++ b/actioncable/README.md @@ -536,6 +536,15 @@ cable.subscriptions.create 'AppearanceChannel',      # normal channel code goes here...  ``` +## Download and Installation + +The latest version of Action Cable can be installed with [RubyGems](#gem-usage), +or with [npm](#npm-usage). + +Source code can be downloaded as part of the Rails project on GitHub + +* https://github.com/rails/rails/tree/master/actioncable +  ## License  Action Cable is released under the MIT license: diff --git a/actioncable/actioncable.gemspec b/actioncable/actioncable.gemspec index e7f91d0e34..6d95f022fa 100644 --- a/actioncable/actioncable.gemspec +++ b/actioncable/actioncable.gemspec @@ -20,6 +20,6 @@ Gem::Specification.new do |s|    s.add_dependency "actionpack", version -  s.add_dependency "nio4r",            "~> 1.2" +  s.add_dependency "nio4r",            "~> 2.0"    s.add_dependency "websocket-driver", "~> 0.6.1"  end diff --git a/actioncable/app/assets/javascripts/action_cable/connection.coffee b/actioncable/app/assets/javascripts/action_cable/connection.coffee index 29ad676290..7fd68cad2f 100644 --- a/actioncable/app/assets/javascripts/action_cable/connection.coffee +++ b/actioncable/app/assets/javascripts/action_cable/connection.coffee @@ -23,7 +23,7 @@ class ActionCable.Connection    open: =>      if @isActive()        ActionCable.log("Attempted to open WebSocket, but existing socket is #{@getState()}") -      throw new Error("Existing connection must be closed before opening") +      false      else        ActionCable.log("Opening WebSocket, current state is #{@getState()}, subprotocols: #{protocols}")        @uninstallEventHandlers() if @webSocket? diff --git a/actioncable/bin/test b/actioncable/bin/test new file mode 100755 index 0000000000..a7beb14b27 --- /dev/null +++ b/actioncable/bin/test @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby + +COMPONENT_ROOT = File.expand_path("..", __dir__) +require File.expand_path("../tools/test", COMPONENT_ROOT) diff --git a/actioncable/lib/action_cable.rb b/actioncable/lib/action_cable.rb index d353716636..c2d3550acb 100644 --- a/actioncable/lib/action_cable.rb +++ b/actioncable/lib/action_cable.rb @@ -1,5 +1,5 @@  #-- -# Copyright (c) 2015-2016 Basecamp, LLC +# Copyright (c) 2015-2017 Basecamp, LLC  #  # Permission is hereby granted, free of charge, to any person obtaining  # a copy of this software and associated documentation files (the diff --git a/actioncable/lib/action_cable/connection/client_socket.rb b/actioncable/lib/action_cable/connection/client_socket.rb index 70a2bbecb1..c7e30e78c8 100644 --- a/actioncable/lib/action_cable/connection/client_socket.rb +++ b/actioncable/lib/action_cable/connection/client_socket.rb @@ -90,8 +90,8 @@ module ActionCable          reason ||= ""          unless code == 1000 || (code >= 3000 && code <= 4999) -          raise ArgumentError, "Failed to execute 'close' on WebSocket: " + -                               "The code must be either 1000, or between 3000 and 4999. " + +          raise ArgumentError, "Failed to execute 'close' on WebSocket: " \ +                               "The code must be either 1000, or between 3000 and 4999. " \                                 "#{code} is neither."          end diff --git a/actioncable/lib/action_cable/connection/subscriptions.rb b/actioncable/lib/action_cable/connection/subscriptions.rb index abf42c99d5..44bce1e195 100644 --- a/actioncable/lib/action_cable/connection/subscriptions.rb +++ b/actioncable/lib/action_cable/connection/subscriptions.rb @@ -19,7 +19,7 @@ module ActionCable            logger.error "Received unrecognized command in #{data.inspect}"          end        rescue Exception => e -        logger.error "Could not execute command from #{data.inspect}) [#{e.class} - #{e.message}]: #{e.backtrace.first(5).join(" | ")}" +        logger.error "Could not execute command from (#{data.inspect}) [#{e.class} - #{e.message}]: #{e.backtrace.first(5).join(" | ")}"        end        def add(data) diff --git a/actioncable/lib/action_cable/engine.rb b/actioncable/lib/action_cable/engine.rb index e23527b84e..63a26636a0 100644 --- a/actioncable/lib/action_cable/engine.rb +++ b/actioncable/lib/action_cable/engine.rb @@ -31,7 +31,7 @@ module ActionCable            self.cable = Rails.application.config_for(config_path).with_indifferent_access          end -        previous_connection_class = self.connection_class +        previous_connection_class = connection_class          self.connection_class = -> { "ApplicationCable::Connection".safe_constantize || previous_connection_class.call }          options.each { |k, v| send("#{k}=", v) } diff --git a/actioncable/lib/action_cable/subscription_adapter.rb b/actioncable/lib/action_cable/subscription_adapter.rb index 72e62f3daf..596269ab9b 100644 --- a/actioncable/lib/action_cable/subscription_adapter.rb +++ b/actioncable/lib/action_cable/subscription_adapter.rb @@ -4,5 +4,6 @@ module ActionCable      autoload :Base      autoload :SubscriberMap +    autoload :ChannelPrefix    end  end diff --git a/actioncable/lib/action_cable/subscription_adapter/channel_prefix.rb b/actioncable/lib/action_cable/subscription_adapter/channel_prefix.rb new file mode 100644 index 0000000000..8b293cc785 --- /dev/null +++ b/actioncable/lib/action_cable/subscription_adapter/channel_prefix.rb @@ -0,0 +1,26 @@ +module ActionCable +  module SubscriptionAdapter +    module ChannelPrefix # :nodoc: +      def broadcast(channel, payload) +        channel = channel_with_prefix(channel) +        super +      end + +      def subscribe(channel, callback, success_callback = nil) +        channel = channel_with_prefix(channel) +        super +      end + +      def unsubscribe(channel, callback) +        channel = channel_with_prefix(channel) +        super +      end + +      private +        # Returns the channel name, including channel_prefix specified in cable.yml +        def channel_with_prefix(channel) +          [@server.config.cable[:channel_prefix], channel].compact.join(":") +        end +    end +  end +end diff --git a/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb b/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb index c3018c5281..56b068976b 100644 --- a/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb +++ b/actioncable/lib/action_cable/subscription_adapter/evented_redis.rb @@ -11,6 +11,8 @@ EventMachine.kqueue if EventMachine.kqueue?  module ActionCable    module SubscriptionAdapter      class EventedRedis < Base # :nodoc: +      prepend ChannelPrefix +        @@mutex = Mutex.new        # Overwrite this factory method for EventMachine Redis connections if you want to use a different Redis connection library than EM::Hiredis. diff --git a/actioncable/lib/action_cable/subscription_adapter/redis.rb b/actioncable/lib/action_cable/subscription_adapter/redis.rb index 62bd284a6b..41a6e55822 100644 --- a/actioncable/lib/action_cable/subscription_adapter/redis.rb +++ b/actioncable/lib/action_cable/subscription_adapter/redis.rb @@ -6,6 +6,8 @@ require "redis"  module ActionCable    module SubscriptionAdapter      class Redis < Base # :nodoc: +      prepend ChannelPrefix +        # Overwrite this factory method for redis connections if you want to use a different Redis library than Redis.        # This is needed, for example, when using Makara proxies for distributed Redis.        cattr_accessor(:redis_connector) { ->(config) { ::Redis.new(url: config[:url]) } } diff --git a/actioncable/lib/rails/generators/channel/channel_generator.rb b/actioncable/lib/rails/generators/channel/channel_generator.rb index 04b787c3a4..984b78bc9c 100644 --- a/actioncable/lib/rails/generators/channel/channel_generator.rb +++ b/actioncable/lib/rails/generators/channel/channel_generator.rb @@ -13,7 +13,7 @@ module Rails          template "channel.rb", File.join("app/channels", class_path, "#{file_name}_channel.rb")          if options[:assets] -          if self.behavior == :invoke +          if behavior == :invoke              template "assets/cable.js", "app/assets/javascripts/cable.js"            end @@ -30,7 +30,7 @@ module Rails          # FIXME: Change these files to symlinks once RubyGems 2.5.0 is required.          def generate_application_cable_files -          return if self.behavior != :invoke +          return if behavior != :invoke            files = [              "application_cable/channel.rb", diff --git a/actioncable/test/channel/periodic_timers_test.rb b/actioncable/test/channel/periodic_timers_test.rb index 0cc4992ef6..17a8e45a35 100644 --- a/actioncable/test/channel/periodic_timers_test.rb +++ b/actioncable/test/channel/periodic_timers_test.rb @@ -38,23 +38,26 @@ class ActionCable::Channel::PeriodicTimersTest < ActiveSupport::TestCase    test "disallow negative and zero periods" do      [ 0, 0.0, 0.seconds, -1, -1.seconds, "foo", :foo, Object.new ].each do |invalid| -      assert_raise ArgumentError, /Expected every:/ do +      e = assert_raise ArgumentError do          ChatChannel.periodically :send_updates, every: invalid        end +      assert_match(/Expected every:/, e.message)      end    end    test "disallow block and arg together" do -    assert_raise ArgumentError, /not both/ do +    e = assert_raise ArgumentError do        ChatChannel.periodically(:send_updates, every: 1) { ping }      end +    assert_match(/not both/, e.message)    end    test "disallow unknown args" do      [ "send_updates", Object.new, nil ].each do |invalid| -      assert_raise ArgumentError, /Expected a Symbol/ do +      e = assert_raise ArgumentError do          ChatChannel.periodically invalid, every: 1        end +      assert_match(/Expected a Symbol/, e.message)      end    end diff --git a/actioncable/test/javascript/src/unit/consumer_test.coffee b/actioncable/test/javascript/src/unit/consumer_test.coffee index cf8a592255..41445274eb 100644 --- a/actioncable/test/javascript/src/unit/consumer_test.coffee +++ b/actioncable/test/javascript/src/unit/consumer_test.coffee @@ -2,8 +2,11 @@  {consumerTest} = ActionCable.TestHelpers  module "ActionCable.Consumer", -> -  consumerTest "#connect", connect: false, ({consumer, server, done}) -> -    server.on("connection", done) +  consumerTest "#connect", connect: false, ({consumer, server, assert, done}) -> +    server.on "connection", -> +      assert.equal consumer.connect(), false +      done() +      consumer.connect()    consumerTest "#disconnect", ({consumer, client, done}) -> diff --git a/actioncable/test/subscription_adapter/channel_prefix.rb b/actioncable/test/subscription_adapter/channel_prefix.rb new file mode 100644 index 0000000000..9ad659912e --- /dev/null +++ b/actioncable/test/subscription_adapter/channel_prefix.rb @@ -0,0 +1,36 @@ +require "test_helper" + +class ActionCable::Server::WithIndependentConfig < ActionCable::Server::Base +  # ActionCable::Server::Base defines config as a class variable. +  # Need config to be an instance variable here as we're testing 2 separate configs +  def config +    @config ||= ActionCable::Server::Configuration.new +  end +end + +module ChannelPrefixTest +  def test_channel_prefix +    server2 = ActionCable::Server::WithIndependentConfig.new +    server2.config.cable = alt_cable_config +    server2.config.logger = Logger.new(StringIO.new).tap { |l| l.level = Logger::UNKNOWN } + +    adapter_klass = server2.config.pubsub_adapter + +    rx_adapter2 = adapter_klass.new(server2) +    tx_adapter2 = adapter_klass.new(server2) + +    subscribe_as_queue("channel") do |queue| +      subscribe_as_queue("channel", rx_adapter2) do |queue2| +        @tx_adapter.broadcast("channel", "hello world") +        tx_adapter2.broadcast("channel", "hello world 2") + +        assert_equal "hello world", queue.pop +        assert_equal "hello world 2", queue2.pop +      end +    end +  end + +  def alt_cable_config +    cable_config.merge(channel_prefix: "foo") +  end +end diff --git a/actioncable/test/subscription_adapter/evented_redis_test.rb b/actioncable/test/subscription_adapter/evented_redis_test.rb index 2401950aa7..c55d35848e 100644 --- a/actioncable/test/subscription_adapter/evented_redis_test.rb +++ b/actioncable/test/subscription_adapter/evented_redis_test.rb @@ -1,8 +1,10 @@  require "test_helper"  require_relative "./common" +require_relative "./channel_prefix"  class EventedRedisAdapterTest < ActionCable::TestCase    include CommonSubscriptionAdapterTest +  include ChannelPrefixTest    def setup      super diff --git a/actioncable/test/subscription_adapter/redis_test.rb b/actioncable/test/subscription_adapter/redis_test.rb index 2ba5636656..4df5e0cbcd 100644 --- a/actioncable/test/subscription_adapter/redis_test.rb +++ b/actioncable/test/subscription_adapter/redis_test.rb @@ -1,8 +1,10 @@  require "test_helper"  require_relative "./common" +require_relative "./channel_prefix"  class RedisAdapterTest < ActionCable::TestCase    include CommonSubscriptionAdapterTest +  include ChannelPrefixTest    def cable_config      { adapter: "redis", driver: "ruby", url: "redis://127.0.0.1:6379/12" } diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md index 3b9f503a0b..6ec10c7a70 100644 --- a/actionmailer/CHANGELOG.md +++ b/actionmailer/CHANGELOG.md @@ -1,3 +1,28 @@ +*   Add `:args` to `process.action_mailer` event. + +    *Yuji Yaginuma* + +*   Add parameterized invocation of mailers as a way to share before filters and defaults between actions. +    See ActionMailer::Parameterized for a full example of the benefit. + +    *DHH* + +*   Allow lambdas to be used as lazy defaults in addition to procs. + +    *DHH* + +*   Mime type: allow to custom content type when setting body in headers +    and attachments. + +    Example: + +        def test_emails +          attachments["invoice.pdf"] = "This is test File content" +          mail(body: "Hello there", content_type: "text/html") +        end + +    *Minh Quy* +  *   Exception handling: use `rescue_from` to handle exceptions raised by      mailer actions, by message delivery, and by deferred delivery jobs. diff --git a/actionmailer/MIT-LICENSE b/actionmailer/MIT-LICENSE index 8573eb1225..ac810e86d0 100644 --- a/actionmailer/MIT-LICENSE +++ b/actionmailer/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2016 David Heinemeier Hansson +Copyright (c) 2004-2017 David Heinemeier Hansson  Permission is hereby granted, free of charge, to any person obtaining  a copy of this software and associated documentation files (the diff --git a/actionmailer/lib/action_mailer.rb b/actionmailer/lib/action_mailer.rb index 668bc99435..211190560a 100644 --- a/actionmailer/lib/action_mailer.rb +++ b/actionmailer/lib/action_mailer.rb @@ -1,5 +1,5 @@  #-- -# Copyright (c) 2004-2016 David Heinemeier Hansson +# Copyright (c) 2004-2017 David Heinemeier Hansson  #  # Permission is hereby granted, free of charge, to any person obtaining  # a copy of this software and associated documentation files (the @@ -42,6 +42,7 @@ module ActionMailer    autoload :DeliveryMethods    autoload :InlinePreviewInterceptor    autoload :MailHelper +  autoload :Parameterized    autoload :Preview    autoload :Previews, "action_mailer/preview"    autoload :TestCase diff --git a/actionmailer/lib/action_mailer/base.rb b/actionmailer/lib/action_mailer/base.rb index 19408f2a48..444be944df 100644 --- a/actionmailer/lib/action_mailer/base.rb +++ b/actionmailer/lib/action_mailer/base.rb @@ -208,6 +208,19 @@ module ActionMailer    #       end    #     end    # +  # You can also send attachments with html template, in this case you need to add body, attachments, +  # and custom content type like this: +  # +  #     class NotifierMailer < ApplicationMailer +  #       def welcome(recipient) +  #         attachments["free_book.pdf"] = File.read("path/to/file.pdf") +  #         mail(to: recipient, +  #              subject: "New account information", +  #              content_type: "text/html", +  #              body: "<html><body>Hello there</body></html>") +  #       end +  #     end +  #    # = Inline Attachments    #    # You can also specify that a file should be displayed inline with other HTML. This is useful @@ -275,20 +288,19 @@ module ActionMailer    #             content_description: 'This is a description'    #   end    # -  # Finally, Action Mailer also supports passing <tt>Proc</tt> objects into the default hash, so you -  # can define methods that evaluate as the message is being generated: +  # Finally, Action Mailer also supports passing <tt>Proc</tt> and <tt>Lambda</tt> objects into the default hash, +  # so you can define methods that evaluate as the message is being generated:    #    #   class NotifierMailer < ApplicationMailer -  #     default 'X-Special-Header' => Proc.new { my_method } +  #     default 'X-Special-Header' => Proc.new { my_method }, to: -> { @inviter.email_address }    #    #     private -  #    #       def my_method    #         'some complex call'    #       end    #   end    # -  # Note that the proc is evaluated right at the start of the mail message generation, so if you +  # Note that the proc/lambda is evaluated right at the start of the mail message generation, so if you    # set something in the default hash using a proc, and then set the same thing inside of your    # mailer method, it will get overwritten by the mailer method.    # @@ -311,7 +323,6 @@ module ActionMailer    #     end    #    #     private -  #    #       def add_inline_attachment!    #         attachments.inline["footer.jpg"] = File.read('/path/to/filename.jpg')    #       end @@ -417,10 +428,11 @@ module ActionMailer    # * <tt>deliveries</tt> - Keeps an array of all the emails sent out through the Action Mailer with    #   <tt>delivery_method :test</tt>. Most useful for unit and functional testing.    # -  # * <tt>deliver_later_queue_name</tt> - The name of the queue used with <tt>deliver_later</tt>. +  # * <tt>deliver_later_queue_name</tt> - The name of the queue used with <tt>deliver_later</tt>. Defaults to +mailers+.    class Base < AbstractController::Base      include DeliveryMethods      include Rescuable +    include Parameterized      include Previews      abstract! @@ -567,7 +579,7 @@ module ActionMailer        end        def respond_to_missing?(method, include_all = false) -        action_methods.include?(method.to_s) +        action_methods.include?(method.to_s) || super        end      end @@ -586,7 +598,8 @@ module ActionMailer      def process(method_name, *args) #:nodoc:        payload = {          mailer: self.class.name, -        action: method_name +        action: method_name, +        args: args        }        ActiveSupport::Notifications.instrument("process.action_mailer", payload) do @@ -875,7 +888,7 @@ module ActionMailer          default_values = self.class.default.map do |key, value|            [              key, -            value.is_a?(Proc) ? instance_eval(&value) : value +            value.is_a?(Proc) ? instance_exec(&value) : value            ]          end.to_h @@ -896,15 +909,19 @@ module ActionMailer            yield(collector)            collector.responses          elsif headers[:body] -          [{ -            body: headers.delete(:body), -            content_type: self.class.default[:content_type] || "text/plain" -          }] +          collect_responses_from_text(headers)          else            collect_responses_from_templates(headers)          end        end +      def collect_responses_from_text(headers) +        [{ +          body: headers.delete(:body), +          content_type: headers[:content_type] || "text/plain" +        }] +      end +        def collect_responses_from_templates(headers)          templates_path = headers[:template_path] || self.class.mailer_name          templates_name = headers[:template_name] || action_name diff --git a/actionmailer/lib/action_mailer/delivery_methods.rb b/actionmailer/lib/action_mailer/delivery_methods.rb index be98f4c65e..bcc4ef03cf 100644 --- a/actionmailer/lib/action_mailer/delivery_methods.rb +++ b/actionmailer/lib/action_mailer/delivery_methods.rb @@ -59,7 +59,7 @@ module ActionMailer        end        def wrap_delivery_behavior(mail, method = nil, options = nil) # :nodoc: -        method ||= self.delivery_method +        method ||= delivery_method          mail.delivery_handler = self          case method diff --git a/actionmailer/lib/action_mailer/inline_preview_interceptor.rb b/actionmailer/lib/action_mailer/inline_preview_interceptor.rb index 9087d335fa..980415afe0 100644 --- a/actionmailer/lib/action_mailer/inline_preview_interceptor.rb +++ b/actionmailer/lib/action_mailer/inline_preview_interceptor.rb @@ -26,7 +26,7 @@ module ActionMailer      def transform! #:nodoc:        return message if html_part.blank? -      html_source.gsub!(PATTERN) do |match| +      html_part.body = html_part.decoded.gsub(PATTERN) do |match|          if part = find_part(match[9..-2])            %[src="#{data_url(part)}"]          else @@ -46,10 +46,6 @@ module ActionMailer          @html_part ||= message.html_part        end -      def html_source -        html_part.body.raw_source -      end -        def data_url(part)          "data:#{part.mime_type};base64,#{strict_encode64(part.body.raw_source)}"        end diff --git a/actionmailer/lib/action_mailer/parameterized.rb b/actionmailer/lib/action_mailer/parameterized.rb new file mode 100644 index 0000000000..3acacc1f14 --- /dev/null +++ b/actionmailer/lib/action_mailer/parameterized.rb @@ -0,0 +1,152 @@ +module ActionMailer +  # Provides the option to parameterize mailers in order to share instance variable +  # setup, processing, and common headers. +  # +  # Consider this example that does not use parameterization: +  # +  #   class InvitationsMailer < ApplicationMailer +  #     def account_invitation(inviter, invitee) +  #       @account = inviter.account +  #       @inviter = inviter +  #       @invitee = invitee +  # +  #       subject = "#{@inviter.name} invited you to their Basecamp (#{@account.name})" +  # +  #       mail \ +  #         subject:   subject, +  #         to:        invitee.email_address, +  #         from:      common_address(inviter), +  #         reply_to:  inviter.email_address_with_name +  #     end +  # +  #     def project_invitation(project, inviter, invitee) +  #       @account = inviter.account +  #       @project = project +  #       @inviter = inviter +  #       @invitee = invitee +  #       @summarizer = ProjectInvitationSummarizer.new(@project.bucket) +  # +  #       subject = "#{@inviter.name.familiar} added you to a project in Basecamp (#{@account.name})" +  # +  #       mail \ +  #         subject:   subject, +  #         to:        invitee.email_address, +  #         from:      common_address(inviter), +  #         reply_to:  inviter.email_address_with_name +  #     end +  # +  #     def bulk_project_invitation(projects, inviter, invitee) +  #       @account  = inviter.account +  #       @projects = projects.sort_by(&:name) +  #       @inviter  = inviter +  #       @invitee  = invitee +  # +  #       subject = "#{@inviter.name.familiar} added you to some new stuff in Basecamp (#{@account.name})" +  # +  #       mail \ +  #         subject:   subject, +  #         to:        invitee.email_address, +  #         from:      common_address(inviter), +  #         reply_to:  inviter.email_address_with_name +  #     end +  #   end +  # +  #   InvitationsMailer.account_invitation(person_a, person_b).deliver_later +  # +  # Using parameterized mailers, this can be rewritten as: +  # +  #   class InvitationsMailer < ApplicationMailer +  #     before_action { @inviter, @invitee = params[:inviter], params[:invitee] } +  #     before_action { @account = params[:inviter].account } +  # +  #     default to:       -> { @invitee.email_address }, +  #             from:     -> { common_address(@inviter) }, +  #             reply_to: -> { @inviter.email_address_with_name } +  # +  #     def account_invitation +  #       mail subject: "#{@inviter.name} invited you to their Basecamp (#{@account.name})" +  #     end +  # +  #     def project_invitation +  #       @project    = params[:project] +  #       @summarizer = ProjectInvitationSummarizer.new(@project.bucket) +  # +  #       mail subject: "#{@inviter.name.familiar} added you to a project in Basecamp (#{@account.name})" +  #     end +  # +  #     def bulk_project_invitation +  #       @projects = params[:projects].sort_by(&:name) +  # +  #       mail subject: "#{@inviter.name.familiar} added you to some new stuff in Basecamp (#{@account.name})" +  #     end +  #   end +  # +  #   InvitationsMailer.with(inviter: person_a, invitee: person_b).account_invitation.deliver_later +  module Parameterized +    extend ActiveSupport::Concern + +    included do +      attr_accessor :params +    end + +    module ClassMethods +      # Provide the parameters to the mailer in order to use them in the instance methods and callbacks. +      # +      #   InvitationsMailer.with(inviter: person_a, invitee: person_b).account_invitation.deliver_later +      # +      # See Parameterized documentation for full example. +      def with(params) +        ActionMailer::Parameterized::Mailer.new(self, params) +      end +    end + +    class Mailer # :nodoc: +      def initialize(mailer, params) +        @mailer, @params = mailer, params +      end + +      private +        def method_missing(method_name, *args) +          if @mailer.action_methods.include?(method_name.to_s) +            ActionMailer::Parameterized::MessageDelivery.new(@mailer, method_name, @params, *args) +          else +            super +          end +        end + +        def respond_to_missing?(method, include_all = false) +          @mailer.respond_to?(method, include_all) +        end +    end + +    class MessageDelivery < ActionMailer::MessageDelivery # :nodoc: +      def initialize(mailer_class, action, params, *args) +        super(mailer_class, action, *args) +        @params = params +      end + +      private +        def processed_mailer +          @processed_mailer ||= @mailer_class.new.tap do |mailer| +            mailer.params = @params +            mailer.process @action, *@args +          end +        end + +        def enqueue_delivery(delivery_method, options = {}) +          if processed? +            super +          else +            args = @mailer_class.name, @action.to_s, delivery_method.to_s, @params, *@args +            ActionMailer::Parameterized::DeliveryJob.set(options).perform_later(*args) +          end +        end +    end + +    class DeliveryJob < ActionMailer::DeliveryJob # :nodoc: +      def perform(mailer, mail_method, delivery_method, params, *args) +        mailer.constantize.with(params).public_send(mail_method, *args).send(delivery_method) +      end +    end +  end +end diff --git a/actionmailer/lib/action_mailer/preview.rb b/actionmailer/lib/action_mailer/preview.rb index c147ca78d0..b0152aff03 100644 --- a/actionmailer/lib/action_mailer/preview.rb +++ b/actionmailer/lib/action_mailer/preview.rb @@ -63,7 +63,7 @@ module ActionMailer        # interceptors will be informed so that they can transform the message        # as they would if the mail was actually being delivered.        def call(email) -        preview = self.new +        preview = new          message = preview.public_send(email)          inform_preview_interceptors(message)          message diff --git a/actionmailer/lib/action_mailer/test_case.rb b/actionmailer/lib/action_mailer/test_case.rb index ebc7b37961..9ead03a40c 100644 --- a/actionmailer/lib/action_mailer/test_case.rb +++ b/actionmailer/lib/action_mailer/test_case.rb @@ -4,8 +4,8 @@ require "rails-dom-testing"  module ActionMailer    class NonInferrableMailerError < ::StandardError      def initialize(name) -      super "Unable to determine the mailer to test from #{name}. " + -        "You'll need to specify it using tests YourMailer in your " + +      super "Unable to determine the mailer to test from #{name}. " \ +        "You'll need to specify it using tests YourMailer in your " \          "test case definition"      end    end @@ -57,7 +57,7 @@ module ActionMailer          end          def mailer_class -          if mailer = self._mailer_class +          if mailer = _mailer_class              mailer            else              tests determine_default_mailer(name) diff --git a/actionmailer/lib/action_mailer/test_helper.rb b/actionmailer/lib/action_mailer/test_helper.rb index c17ecad4c6..c30fb1fc18 100644 --- a/actionmailer/lib/action_mailer/test_helper.rb +++ b/actionmailer/lib/action_mailer/test_helper.rb @@ -88,7 +88,7 @@ module ActionMailer      #     end      #   end      def assert_enqueued_emails(number, &block) -      assert_enqueued_jobs number, only: ActionMailer::DeliveryJob, &block +      assert_enqueued_jobs number, only: [ ActionMailer::DeliveryJob, ActionMailer::Parameterized::DeliveryJob ], &block      end      # Asserts that no emails are enqueued for later delivery. @@ -107,7 +107,7 @@ module ActionMailer      #     end      #   end      def assert_no_enqueued_emails(&block) -      assert_no_enqueued_jobs only: ActionMailer::DeliveryJob, &block +      assert_no_enqueued_jobs only: [ ActionMailer::DeliveryJob, ActionMailer::Parameterized::DeliveryJob ], &block      end    end  end diff --git a/actionmailer/lib/rails/generators/mailer/mailer_generator.rb b/actionmailer/lib/rails/generators/mailer/mailer_generator.rb index 4a8d0178de..99fe4544f1 100644 --- a/actionmailer/lib/rails/generators/mailer/mailer_generator.rb +++ b/actionmailer/lib/rails/generators/mailer/mailer_generator.rb @@ -11,7 +11,7 @@ module Rails          template "mailer.rb", File.join("app/mailers", class_path, "#{file_name}_mailer.rb")          in_root do -          if self.behavior == :invoke && !File.exist?(application_mailer_file_name) +          if behavior == :invoke && !File.exist?(application_mailer_file_name)              template "application_mailer.rb", application_mailer_file_name            end          end diff --git a/actionmailer/test/abstract_unit.rb b/actionmailer/test/abstract_unit.rb index 82f03f5cba..a646cbd581 100644 --- a/actionmailer/test/abstract_unit.rb +++ b/actionmailer/test/abstract_unit.rb @@ -3,8 +3,8 @@ require "active_support/core_ext/kernel/reporting"  # These are the normal settings that will be set up by Railties  # TODO: Have these tests support other combinations of these values  silence_warnings do -  Encoding.default_internal = "UTF-8" -  Encoding.default_external = "UTF-8" +  Encoding.default_internal = Encoding::UTF_8 +  Encoding.default_external = Encoding::UTF_8  end  module Rails @@ -31,15 +31,15 @@ I18n.enforce_available_locales = false  FIXTURE_LOAD_PATH = File.expand_path("fixtures", File.dirname(__FILE__))  ActionMailer::Base.view_paths = FIXTURE_LOAD_PATH -# Skips the current run on Rubinius using Minitest::Assertions#skip -def rubinius_skip(message = "") -  skip message if RUBY_ENGINE == "rbx" -end -# Skips the current run on JRuby using Minitest::Assertions#skip -def jruby_skip(message = "") -  skip message if defined?(JRUBY_VERSION) -end -  class ActiveSupport::TestCase    include ActiveSupport::Testing::MethodCallAssertions + +  # Skips the current run on Rubinius using Minitest::Assertions#skip +  private def rubinius_skip(message = "") +    skip message if RUBY_ENGINE == "rbx" +  end +  # Skips the current run on JRuby using Minitest::Assertions#skip +  private def jruby_skip(message = "") +    skip message if defined?(JRUBY_VERSION) +  end  end diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb index 91049e33f9..61960d411d 100644 --- a/actionmailer/test/base_test.rb +++ b/actionmailer/test/base_test.rb @@ -140,6 +140,11 @@ class BaseTest < ActiveSupport::TestCase      assert_equal("multipart/mixed", email.mime_type)    end +  test "set mime type to text/html when attachment is included and body is set" do +    email = BaseMailer.attachment_with_content(body: "Hello there", content_type: "text/html") +    assert_equal("text/html", email.mime_type) +  end +    test "adds the rendered template as part" do      email = BaseMailer.attachment_with_content      assert_equal(2, email.parts.length) @@ -829,6 +834,25 @@ class BaseTest < ActiveSupport::TestCase      assert_equal "special indeed!", mail["X-Special-Header"].to_s    end +  test "notification for process" do +    begin +      events = [] +      ActiveSupport::Notifications.subscribe("process.action_mailer") do |*args| +        events << ActiveSupport::Notifications::Event.new(*args) +      end + +      BaseMailer.welcome(body: "Hello there").deliver_now + +      assert_equal 1, events.length +      assert_equal "process.action_mailer", events[0].name +      assert_equal "BaseMailer", events[0].payload[:mailer] +      assert_equal :welcome, events[0].payload[:action] +      assert_equal [{ body: "Hello there" }], events[0].payload[:args] +    ensure +      ActiveSupport::Notifications.unsubscribe "process.action_mailer" +    end +  end +    private      # Execute the block setting the given values and restoring old values after diff --git a/actionmailer/test/i18n_with_controller_test.rb b/actionmailer/test/i18n_with_controller_test.rb index a87a9c9861..4f09339800 100644 --- a/actionmailer/test/i18n_with_controller_test.rb +++ b/actionmailer/test/i18n_with_controller_test.rb @@ -57,9 +57,7 @@ class ActionMailerI18nWithControllerTest < ActionDispatch::IntegrationTest      stub_any_instance(Mail::SMTP, instance: Mail::SMTP.new({})) do |instance|        assert_called(instance, :deliver!) do          with_translation "de", email_subject: "[Anmeldung] Willkommen" do -          ActiveSupport::Deprecation.silence do -            get "/test/send_mail" -          end +          get "/test/send_mail"            assert_equal "Mail sent - Subject: [Anmeldung] Willkommen", @response.body          end        end diff --git a/actionmailer/test/mail_helper_test.rb b/actionmailer/test/mail_helper_test.rb index a2e04b5f48..6042548aef 100644 --- a/actionmailer/test/mail_helper_test.rb +++ b/actionmailer/test/mail_helper_test.rb @@ -2,11 +2,11 @@ require "abstract_unit"  class HelperMailer < ActionMailer::Base    def use_mail_helper -    @text = "But soft! What light through yonder window breaks? It is the east, " + -            "and Juliet is the sun. Arise, fair sun, and kill the envious moon, " + -            "which is sick and pale with grief that thou, her maid, art far more " + -            "fair than she. Be not her maid, for she is envious! Her vestal " + -            "livery is but sick and green, and none but fools do wear it. Cast " + +    @text = "But soft! What light through yonder window breaks? It is the east, " \ +            "and Juliet is the sun. Arise, fair sun, and kill the envious moon, " \ +            "which is sick and pale with grief that thou, her maid, art far more " \ +            "fair than she. Be not her maid, for she is envious! Her vestal " \ +            "livery is but sick and green, and none but fools do wear it. Cast " \              "it off!"      mail_with_defaults do |format| diff --git a/actionmailer/test/mailers/params_mailer.rb b/actionmailer/test/mailers/params_mailer.rb new file mode 100644 index 0000000000..4c0fae6d91 --- /dev/null +++ b/actionmailer/test/mailers/params_mailer.rb @@ -0,0 +1,11 @@ +class ParamsMailer < ActionMailer::Base +  before_action { @inviter, @invitee = params[:inviter], params[:invitee] } + +  default to: Proc.new { @invitee }, from: -> { @inviter } + +  def invitation +    mail(subject: "Welcome to the project!") do |format| +      format.text { render plain: "So says #{@inviter}" } +    end +  end +end diff --git a/actionmailer/test/parameterized_test.rb b/actionmailer/test/parameterized_test.rb new file mode 100644 index 0000000000..914ed12312 --- /dev/null +++ b/actionmailer/test/parameterized_test.rb @@ -0,0 +1,55 @@ +require "abstract_unit" +require "active_job" +require "mailers/params_mailer" + +class ParameterizedTest < ActiveSupport::TestCase +  include ActiveJob::TestHelper + +  setup do +    @previous_logger = ActiveJob::Base.logger +    ActiveJob::Base.logger = Logger.new(nil) + +    @previous_delivery_method = ActionMailer::Base.delivery_method +    ActionMailer::Base.delivery_method = :test + +    @previous_deliver_later_queue_name = ActionMailer::Base.deliver_later_queue_name +    ActionMailer::Base.deliver_later_queue_name = :test_queue +    ActionMailer::Base.delivery_method = :test + +    @mail = ParamsMailer.with(inviter: "david@basecamp.com", invitee: "jason@basecamp.com").invitation +  end + +  teardown do +    ActiveJob::Base.logger = @previous_logger +    ParamsMailer.deliveries.clear + +    ActionMailer::Base.delivery_method = @previous_delivery_method +    ActionMailer::Base.deliver_later_queue_name = @previous_deliver_later_queue_name +  end + +  test "parameterized headers" do +    assert_equal(["jason@basecamp.com"], @mail.to) +    assert_equal(["david@basecamp.com"], @mail.from) +    assert_equal("So says david@basecamp.com", @mail.body.encoded) +  end + +  test "enqueue the email with params" do +    assert_performed_with(job: ActionMailer::Parameterized::DeliveryJob, args: ["ParamsMailer", "invitation", "deliver_now", { inviter: "david@basecamp.com", invitee: "jason@basecamp.com" } ]) do +      @mail.deliver_later +    end +  end + +  test "respond_to?" do +    mailer = ParamsMailer.with(inviter: "david@basecamp.com", invitee: "jason@basecamp.com") + +    assert_respond_to mailer, :invitation +    assert_not_respond_to mailer, :anything + +    invitation = mailer.method(:invitation) +    assert_equal Method, invitation.class + +    assert_raises(NameError) do +      invitation = mailer.method(:anything) +    end +  end +end diff --git a/actionmailer/test/test_helper_test.rb b/actionmailer/test/test_helper_test.rb index 31ac5a5211..876e9b0634 100644 --- a/actionmailer/test/test_helper_test.rb +++ b/actionmailer/test/test_helper_test.rb @@ -143,6 +143,16 @@ class TestHelperMailerTest < ActionMailer::TestCase      end    end +  def test_assert_enqueued_parameterized_emails +    assert_nothing_raised do +      assert_enqueued_emails 1 do +        silence_stream($stdout) do +          TestHelperMailer.with(a: 1).test.deliver_later +        end +      end +    end +  end +    def test_assert_enqueued_emails_too_few_sent      error = assert_raise ActiveSupport::TestCase::Assertion do        assert_enqueued_emails 2 do @@ -176,6 +186,14 @@ class TestHelperMailerTest < ActionMailer::TestCase      end    end +  def test_assert_no_enqueued_parameterized_emails +    assert_nothing_raised do +      assert_no_enqueued_emails do +        TestHelperMailer.with(a: 1).test.deliver_now +      end +    end +  end +    def test_assert_no_enqueued_emails_failure      error = assert_raise ActiveSupport::TestCase::Assertion do        assert_no_enqueued_emails do diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 3123fc9786..a7a4aabc98 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,15 +1,35 @@ +*   Remove deprecated `.to_prepare`, `.to_cleanup`, `.prepare!` and `.cleanup!` from `ActionDispatch::Reloader`. + +    *Rafael Mendonça França* + +*   Remove deprecated `ActionDispatch::Callbacks.to_prepare` and `ActionDispatch::Callbacks.to_cleanup`. + +    *Rafael Mendonça França* + +*   Remove deprecated `ActionController::Metal.call`. + +    *Rafael Mendonça França* + +*   Remove deprecated `ActionController::Metal#env`. + +    *Rafael Mendonça França* + +*   Make `with_routing` test helper work when testing controllers inheriting from `ActionController::API` + +    *Julia López* +  *   Use accept header in integration tests with `as: :json` -    Instead of appending the `format` to the request path. Rails will figure +    Instead of appending the `format` to the request path, Rails will figure      out the format from the header instead.      This allows devs to use `:as` on routes that don't have a format. -     +      Fixes #27144.      *Kasper Timm Hansen* -*   Reset a new session directly after its creation in ActionDispatch::IntegrationTest#open_session. +*   Reset a new session directly after its creation in `ActionDispatch::IntegrationTest#open_session`.      Fixes #22742. diff --git a/actionpack/MIT-LICENSE b/actionpack/MIT-LICENSE index 8573eb1225..ac810e86d0 100644 --- a/actionpack/MIT-LICENSE +++ b/actionpack/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2016 David Heinemeier Hansson +Copyright (c) 2004-2017 David Heinemeier Hansson  Permission is hereby granted, free of charge, to any person obtaining  a copy of this software and associated documentation files (the diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb index 603c2e9ea7..e7cb6347a2 100644 --- a/actionpack/lib/abstract_controller/base.rb +++ b/actionpack/lib/abstract_controller/base.rb @@ -1,4 +1,3 @@ -require "erubis"  require "abstract_controller/error"  require "active_support/configurable"  require "active_support/descendants_tracker" @@ -22,7 +21,6 @@ module AbstractController      include ActiveSupport::Configurable      extend ActiveSupport::DescendantsTracker -    undef_method :not_implemented      class << self        attr_reader :abstract        alias_method :abstract?, :abstract diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index d339580435..54af938a93 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -109,6 +109,9 @@ module AbstractController      def _process_format(format)      end +    def _process_variant(options) +    end +      def _set_html_content_type # :nodoc:      end @@ -119,10 +122,7 @@ module AbstractController      # :api: private      def _normalize_render(*args, &block)        options = _normalize_args(*args, &block) -      #TODO: remove defined? when we restore AP <=> AV dependency -      if defined?(request) && !request.nil? && request.variant.present? -        options[:variant] = request.variant -      end +      _process_variant(options)        _normalize_options(options)        options      end diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb index 9dab7aeef4..337718afc0 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -118,11 +118,6 @@ module ActionController    class Metal < AbstractController::Base      abstract! -    def env -      @_request.env -    end -    deprecate :env -      # Returns the last part of the controller's name, underscored, without the ending      # <tt>Controller</tt>. For instance, PostsController returns <tt>posts</tt>.      # Namespaces are left out, so Admin::PostsController returns <tt>posts</tt> as well. @@ -232,14 +227,6 @@ module ActionController        middleware_stack      end -    # Makes the controller a Rack endpoint that runs the action in the given -    # +env+'s +action_dispatch.request.path_parameters+ key. -    def self.call(env) -      req = ActionDispatch::Request.new env -      action(req.path_parameters[:action]).call(env) -    end -    class << self; deprecate :call; end -      # Returns a Rack endpoint for the given action name.      def self.action(name)        if middleware_stack.any? diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb index 4dff23dd85..0c50894bce 100644 --- a/actionpack/lib/action_controller/metal/head.rb +++ b/actionpack/lib/action_controller/metal/head.rb @@ -37,7 +37,7 @@ module ActionController        if include_content?(response_code)          self.content_type = content_type || (Mime[formats.first] if formats) -        self.response.charset = false +        response.charset = false        end        true diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb index 30798c1d99..4dfcf4da28 100644 --- a/actionpack/lib/action_controller/metal/redirecting.rb +++ b/actionpack/lib/action_controller/metal/redirecting.rb @@ -50,7 +50,7 @@ module ActionController      #   redirect_to post_url(@post), status: 301, flash: { updated_post_id: @post.id }      #   redirect_to({ action: 'atom' }, alert: "Something serious happened")      # -    def redirect_to(options = {}, response_status = {}) #:doc: +    def redirect_to(options = {}, response_status = {})        raise ActionControllerError.new("Cannot redirect to nil!") unless options        raise AbstractController::DoubleRenderError if response_body diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb index f8a037189c..733aca195d 100644 --- a/actionpack/lib/action_controller/metal/renderers.rb +++ b/actionpack/lib/action_controller/metal/renderers.rb @@ -104,7 +104,7 @@ module ActionController        #        # Since <tt>ActionController::Metal</tt> controllers cannot render, the controller        # must include <tt>AbstractController::Rendering</tt>, <tt>ActionController::Rendering</tt>, -      # and <tt>ActionController::Renderers</tt>, and have at lest one renderer. +      # and <tt>ActionController::Renderers</tt>, and have at least one renderer.        #        # Rather than including <tt>ActionController::Renderers::All</tt> and including all renderers,        # you may specify which renderers to include by passing the renderer name or names to diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb index cdd09e832b..6b17719381 100644 --- a/actionpack/lib/action_controller/metal/rendering.rb +++ b/actionpack/lib/action_controller/metal/rendering.rb @@ -54,6 +54,12 @@ module ActionController      private +      def _process_variant(options) +        if defined?(request) && !request.nil? && request.variant.present? +          options[:variant] = request.variant +        end +      end +        def _render_in_priorities(options)          RENDER_FORMATS_IN_PRIORITY.each do |format|            return options[format] if options.key?(format) @@ -108,7 +114,7 @@ module ActionController          self.status = status if status          self.content_type = content_type if content_type -        self.headers["Location"] = url_for(location) if location +        headers["Location"] = url_for(location) if location          super        end diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb index acfeca1fcb..d304dcf468 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -150,7 +150,7 @@ module ActionController      # permitted flag.      def ==(other)        if other.respond_to?(:permitted?) -        self.permitted? == other.permitted? && self.parameters == other.parameters +        permitted? == other.permitted? && parameters == other.parameters        else          @parameters == other        end @@ -398,8 +398,7 @@ module ActionController        unpermitted_parameters!(params) if self.class.action_on_unpermitted_parameters -      params.permitted = true -      params +      params.permit!      end      # Returns a parameter for the given +key+. If not found, @@ -818,7 +817,6 @@ module ActionController                # Filter this one out.              end            end -          sanitized.permitted = true          end        end diff --git a/actionpack/lib/action_controller/renderer.rb b/actionpack/lib/action_controller/renderer.rb index 3ff80e6a39..acb400cd15 100644 --- a/actionpack/lib/action_controller/renderer.rb +++ b/actionpack/lib/action_controller/renderer.rb @@ -60,7 +60,8 @@ module ActionController      end      # Accepts a custom Rack environment to render templates in. -    # It will be merged with ActionController::Renderer.defaults +    # It will be merged with the default Rack environment defined by +    # +ActionController::Renderer::DEFAULTS+.      def initialize(controller, env, defaults)        @controller = controller        @defaults = defaults diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index 441667e556..7229c67f30 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -354,7 +354,7 @@ module ActionController          end          def controller_class -          if current_controller_class = self._controller_class +          if current_controller_class = _controller_class              current_controller_class            else              self.controller_class = determine_default_controller_class(name) @@ -534,6 +534,7 @@ module ActionController              @request.delete_header "HTTP_ACCEPT"            end            @request.query_string = "" +          @request.env.delete "PATH_INFO"            @response.sent!          end diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb index 7931e53c3e..028177ace2 100644 --- a/actionpack/lib/action_dispatch.rb +++ b/actionpack/lib/action_dispatch.rb @@ -1,5 +1,5 @@  #-- -# Copyright (c) 2004-2016 David Heinemeier Hansson +# Copyright (c) 2004-2017 David Heinemeier Hansson  #  # Permission is hereby granted, free of charge, to any person obtaining  # a copy of this software and associated documentation files (the diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb index 6b718e3682..1a65e2d1cb 100644 --- a/actionpack/lib/action_dispatch/http/mime_type.rb +++ b/actionpack/lib/action_dispatch/http/mime_type.rb @@ -298,7 +298,7 @@ module Mime        end        def respond_to_missing?(method, include_private = false) -        method.to_s.ends_with? "?" +        (method.to_s.ends_with? "?") || super        end    end diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb index ad4aadacf5..8f21eca440 100644 --- a/actionpack/lib/action_dispatch/http/parameters.rb +++ b/actionpack/lib/action_dispatch/http/parameters.rb @@ -22,6 +22,7 @@ module ActionDispatch        included do          class << self +          # Returns the parameter parsers.            attr_reader :parameter_parsers          end @@ -29,7 +30,16 @@ module ActionDispatch        end        module ClassMethods -        def parameter_parsers=(parsers) # :nodoc: +        # Configure the parameter parser for a given mime type. +        # +        # It accepts a hash where the key is the symbol of the mime type +        # and the value is a proc. +        # +        #     original_parsers = ActionDispatch::Request.parameter_parsers +        #     xml_parser = -> (raw_post) { Hash.from_xml(raw_post) || {} } +        #     new_parsers = original_parsers.merge(xml: xml_parser) +        #     ActionDispatch::Request.parameter_parsers = new_parsers +        def parameter_parsers=(parsers)            @parameter_parsers = parsers.transform_keys { |key| key.respond_to?(:symbol) ? key.symbol : key }          end        end diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index a886358399..19fa42ce12 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -85,6 +85,9 @@ module ActionDispatch        end      end +    # Returns true if the request has a header matching the given key parameter. +    # +    #    request.key? :ip_spoofing_check # => true      def key?(key)        has_header? key      end diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index 516a2af69a..dc159596c4 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -425,7 +425,7 @@ module ActionDispatch # :nodoc:      def set_content_type(content_type, charset)        type = (content_type || "").dup -      type << "; charset=#{charset}" if charset +      type << "; charset=#{charset.to_s.downcase}" if charset        set_header CONTENT_TYPE, type      end diff --git a/actionpack/lib/action_dispatch/journey/formatter.rb b/actionpack/lib/action_dispatch/journey/formatter.rb index 20ff4441a0..f3b8e82d32 100644 --- a/actionpack/lib/action_dispatch/journey/formatter.rb +++ b/actionpack/lib/action_dispatch/journey/formatter.rb @@ -36,7 +36,7 @@ module ActionDispatch            route.parts.reverse_each do |key|              break if defaults[key].nil? && parameterized_parts[key].present? -            break if parameterized_parts[key].to_s != defaults[key].to_s +            next if parameterized_parts[key].to_s != defaults[key].to_s              break if required_parts.include?(key)              parameterized_parts.delete(key) @@ -92,7 +92,11 @@ module ActionDispatch            else              routes = non_recursive(cache, options) -            hash = routes.group_by { |_, r| r.score(options) } +            supplied_keys = options.each_with_object({}) do |(k, v), h| +              h[k.to_s] = true if v +            end + +            hash = routes.group_by { |_, r| r.score(supplied_keys) }              hash.keys.sort.reverse_each do |score|                break if score < 0 diff --git a/actionpack/lib/action_dispatch/journey/parser.rb b/actionpack/lib/action_dispatch/journey/parser.rb index db42b64c4b..e002755bcf 100644 --- a/actionpack/lib/action_dispatch/journey/parser.rb +++ b/actionpack/lib/action_dispatch/journey/parser.rb @@ -1,7 +1,7 @@  #  # DO NOT MODIFY!!!!  # This file is automatically generated by Racc 1.4.14 -# from Racc grammer file "". +# from Racc grammar file "".  #  require 'racc/parser.rb' diff --git a/actionpack/lib/action_dispatch/journey/path/pattern.rb b/actionpack/lib/action_dispatch/journey/path/pattern.rb index 0902b9233e..cf0108ec32 100644 --- a/actionpack/lib/action_dispatch/journey/path/pattern.rb +++ b/actionpack/lib/action_dispatch/journey/path/pattern.rb @@ -31,6 +31,13 @@ module ActionDispatch            Visitors::FormatBuilder.new.accept(spec)          end +        def eager_load! +          required_names +          offsets +          to_regexp +          nil +        end +          def ast            @spec.find_all(&:symbol?).each do |node|              re = @requirements[node.to_sym] diff --git a/actionpack/lib/action_dispatch/journey/route.rb b/actionpack/lib/action_dispatch/journey/route.rb index 0cc8d83ac8..927fd369c4 100644 --- a/actionpack/lib/action_dispatch/journey/route.rb +++ b/actionpack/lib/action_dispatch/journey/route.rb @@ -73,6 +73,14 @@ module ActionDispatch          @internal          = internal        end +      def eager_load! +        path.eager_load! +        ast +        parts +        required_defaults +        nil +      end +        def ast          @decorated_ast ||= begin            decorated_ast = path.ast @@ -96,13 +104,18 @@ module ActionDispatch          required_parts + required_defaults.keys        end -      def score(constraints) +      def score(supplied_keys)          required_keys = path.required_names -        supplied_keys = constraints.map { |k, v| v && k.to_s }.compact -        return -1 unless (required_keys - supplied_keys).empty? +        required_keys.each do |k| +          return -1 unless supplied_keys.include?(k) +        end + +        score = 0 +        path.names.each do |k| +          score += 1 if supplied_keys.include?(k) +        end -        score = (supplied_keys & path.names).length          score + (required_defaults.length * 2)        end diff --git a/actionpack/lib/action_dispatch/journey/router.rb b/actionpack/lib/action_dispatch/journey/router.rb index 084ae9325e..d55e1399e4 100644 --- a/actionpack/lib/action_dispatch/journey/router.rb +++ b/actionpack/lib/action_dispatch/journey/router.rb @@ -22,6 +22,13 @@ module ActionDispatch          @routes = routes        end +      def eager_load! +        # Eagerly trigger the simulator's initialization so +        # it doesn't happen during a request cycle. +        simulator +        nil +      end +        def serve(req)          find_routes(req).each do |match, parameters, route|            set_params  = req.path_parameters diff --git a/actionpack/lib/action_dispatch/journey/visitors.rb b/actionpack/lib/action_dispatch/journey/visitors.rb index cda859cba4..1c50192867 100644 --- a/actionpack/lib/action_dispatch/journey/visitors.rb +++ b/actionpack/lib/action_dispatch/journey/visitors.rb @@ -5,7 +5,7 @@ module ActionDispatch        ESCAPE_PATH    = ->(value) { Router::Utils.escape_path(value) }        ESCAPE_SEGMENT = ->(value) { Router::Utils.escape_segment(value) } -      class Parameter < Struct.new(:name, :escaper) +      Parameter = Struct.new(:name, :escaper) do          def escape(value); escaper.call value; end        end diff --git a/actionpack/lib/action_dispatch/middleware/callbacks.rb b/actionpack/lib/action_dispatch/middleware/callbacks.rb index fef246532b..ff129cf96a 100644 --- a/actionpack/lib/action_dispatch/middleware/callbacks.rb +++ b/actionpack/lib/action_dispatch/middleware/callbacks.rb @@ -1,4 +1,3 @@ -  module ActionDispatch    # Provides callbacks to be executed before and after dispatching the request.    class Callbacks @@ -7,17 +6,6 @@ module ActionDispatch      define_callbacks :call      class << self -      def to_prepare(*args, &block) -        ActiveSupport::Reloader.to_prepare(*args, &block) -      end - -      def to_cleanup(*args, &block) -        ActiveSupport::Reloader.to_complete(*args, &block) -      end - -      deprecate to_prepare: "use ActiveSupport::Reloader.to_prepare instead", -        to_cleanup: "use ActiveSupport::Reloader.to_complete instead" -        def before(*args, &block)          set_callback(:call, :before, *args, &block)        end diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index 956c53e813..c61cb3fd68 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -572,7 +572,7 @@ module ActionDispatch          super          if ActiveSupport::LegacyKeyGenerator === key_generator -          raise "You didn't set secrets.secret_key_base, which is required for this cookie jar. " + +          raise "You didn't set secrets.secret_key_base, which is required for this cookie jar. " \              "Read the upgrade documentation to learn more about this new config option."          end diff --git a/actionpack/lib/action_dispatch/middleware/reloader.rb b/actionpack/lib/action_dispatch/middleware/reloader.rb index 90c64037aa..6d64b1424b 100644 --- a/actionpack/lib/action_dispatch/middleware/reloader.rb +++ b/actionpack/lib/action_dispatch/middleware/reloader.rb @@ -1,54 +1,10 @@  module ActionDispatch -  # ActionDispatch::Reloader provides prepare and cleanup callbacks, -  # intended to assist with code reloading during development. -  # -  # Prepare callbacks are run before each request, and cleanup callbacks -  # after each request. In this respect they are analogs of ActionDispatch::Callback's -  # before and after callbacks. However, cleanup callbacks are not called until the -  # request is fully complete -- that is, after #close has been called on -  # the response body. This is important for streaming responses such as the -  # following: -  # -  #     self.response_body = -> (response, output) do -  #       # code here which refers to application models -  #     end -  # -  # Cleanup callbacks will not be called until after the response_body lambda -  # is evaluated, ensuring that it can refer to application models and other -  # classes before they are unloaded. +  # ActionDispatch::Reloader wraps the request with callbacks provided by ActiveSupport::Reloader +  # callbacks, intended to assist with code reloading during development.    #    # By default, ActionDispatch::Reloader is included in the middleware stack    # only in the development environment; specifically, when +config.cache_classes+ -  # is false. Callbacks may be registered even when it is not included in the -  # middleware stack, but are executed only when <tt>ActionDispatch::Reloader.prepare!</tt> -  # or <tt>ActionDispatch::Reloader.cleanup!</tt> are called manually. -  # +  # is false.    class Reloader < Executor -    def self.to_prepare(*args, &block) -      ActiveSupport::Reloader.to_prepare(*args, &block) -    end - -    def self.to_cleanup(*args, &block) -      ActiveSupport::Reloader.to_complete(*args, &block) -    end - -    def self.prepare! -      default_reloader.prepare! -    end - -    def self.cleanup! -      default_reloader.reload! -    end - -    class << self -      attr_accessor :default_reloader # :nodoc: - -      deprecate to_prepare: "use ActiveSupport::Reloader.to_prepare instead", -        to_cleanup: "use ActiveSupport::Reloader.to_complete instead", -        prepare!: "use Rails.application.reloader.prepare! instead", -        cleanup!: "use Rails.application.reloader.reload! instead of cleanup + prepare" -    end - -    self.default_reloader = ActiveSupport::Reloader    end  end diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb index 9f1ae80b97..8bae5bfeff 100644 --- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb +++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb @@ -131,8 +131,8 @@ module ActionDispatch          should_check_ip = @check_ip && client_ips.last && forwarded_ips.last          if should_check_ip && !forwarded_ips.include?(client_ips.last)            # We don't know which came from the proxy, and which from the user -          raise IpSpoofAttackError, "IP spoofing attack?! " + -            "HTTP_CLIENT_IP=#{@req.client_ip.inspect} " + +          raise IpSpoofAttackError, "IP spoofing attack?! " \ +            "HTTP_CLIENT_IP=#{@req.client_ip.inspect} " \              "HTTP_X_FORWARDED_FOR=#{@req.x_forwarded_for.inspect}"          end diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb index 97c937b0b1..d9f018c8ac 100644 --- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb @@ -8,8 +8,8 @@ module ActionDispatch    module Session      class SessionRestoreError < StandardError #:nodoc:        def initialize -        super("Session contains objects whose class definition isn't available.\n" + -          "Remember to require the classes for all objects kept in the session.\n" + +        super("Session contains objects whose class definition isn't available.\n" \ +          "Remember to require the classes for all objects kept in the session.\n" \            "(Original exception: #{$!.message} [#{$!.class}])\n")          set_backtrace $!.backtrace        end diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb index 5c71f0fc48..5d10129d21 100644 --- a/actionpack/lib/action_dispatch/middleware/static.rb +++ b/actionpack/lib/action_dispatch/middleware/static.rb @@ -33,7 +33,7 @@ module ActionDispatch        paths = [path, "#{path}#{ext}", "#{path}/#{@index}#{ext}"]        if match = paths.detect { |p| -        path = File.join(@root, p.force_encoding("UTF-8".freeze)) +        path = File.join(@root, p.force_encoding(Encoding::UTF_8))          begin            File.file?(path) && File.readable?(path)          rescue SystemCallError diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb index 48cc91bbfa..16a18a7f25 100644 --- a/actionpack/lib/action_dispatch/railtie.rb +++ b/actionpack/lib/action_dispatch/railtie.rb @@ -39,8 +39,6 @@ module ActionDispatch        config.action_dispatch.always_write_cookie = Rails.env.development? if config.action_dispatch.always_write_cookie.nil?        ActionDispatch::Cookies::CookieJar.always_write_cookie = config.action_dispatch.always_write_cookie -      ActionDispatch::Reloader.default_reloader = app.reloader -        ActionDispatch.test_app = app      end    end diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb index fe8bf6a35c..c554ce98bc 100644 --- a/actionpack/lib/action_dispatch/routing.rb +++ b/actionpack/lib/action_dispatch/routing.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/string/filters' +require "active_support/core_ext/string/filters"  module ActionDispatch    # The routing module provides URL rewriting in native Ruby. It's a way to diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 089aa9f78e..8d9f70e3c6 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -1,6 +1,7 @@  require "active_support/core_ext/hash/slice"  require "active_support/core_ext/enumerable"  require "active_support/core_ext/array/extract_options" +require "active_support/core_ext/regexp"  require "action_dispatch/routing/redirection"  require "action_dispatch/routing/endpoint" @@ -238,7 +239,7 @@ module ActionDispatch                options[:controller] ||= /.+?/              end -            if to.respond_to? :call +            if to.respond_to?(:action) || to.respond_to?(:call)                options              else                to_endpoint = split_to to @@ -290,16 +291,14 @@ module ActionDispatch            end            def app(blocks) -            if to.is_a?(Class) && to < ActionController::Metal +            if to.respond_to?(:action)                Routing::RouteSet::StaticDispatcher.new to +            elsif to.respond_to?(:call) +              Constraints.new(to, blocks, Constraints::CALL) +            elsif blocks.any? +              Constraints.new(dispatcher(defaults.key?(:controller)), blocks, Constraints::SERVE)              else -              if to.respond_to?(:call) -                Constraints.new(to, blocks, Constraints::CALL) -              elsif blocks.any? -                Constraints.new(dispatcher(defaults.key?(:controller)), blocks, Constraints::SERVE) -              else -                dispatcher(defaults.key?(:controller)) -              end +              dispatcher(defaults.key?(:controller))              end            end diff --git a/actionpack/lib/action_dispatch/routing/redirection.rb b/actionpack/lib/action_dispatch/routing/redirection.rb index 4e2318a45e..dabc045007 100644 --- a/actionpack/lib/action_dispatch/routing/redirection.rb +++ b/actionpack/lib/action_dispatch/routing/redirection.rb @@ -137,6 +137,9 @@ module ActionDispatch        #        #   get "/stories" => redirect("/posts")        # +      # This will redirect the user, while ignoring certain parts of the request, including query string, etc. +      # `/stories`, `/stories?foo=bar`, etc all redirect to `/posts`. +      #        # You can also use interpolation in the supplied redirect argument:        #        #   get 'docs/:article', to: redirect('/wiki/%{article}') @@ -165,6 +168,11 @@ module ActionDispatch        #        #   get 'stores/:name',       to: redirect(subdomain: 'stores', path: '/%{name}')        #   get 'stores/:name(*all)', to: redirect(subdomain: 'stores', path: '/%{name}%{all}') +      #   get '/stories', to: redirect(path: '/posts') +      # +      # This will redirect the user, while changing only the specified parts of the request, +      # for example the `path` option in the last example. +      # `/stories`, `/stories?foo=bar`, redirect to `/posts` and `/posts?foo=bar` respectively.        #        # Finally, an object which responds to call can be supplied to redirect, allowing you to reuse        # common redirect routes. The call method must accept two arguments, params and request, and return diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 5853adb110..5b873aeab7 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -349,6 +349,12 @@ module ActionDispatch          @formatter = Journey::Formatter.new self        end +      def eager_load! +        router.eager_load! +        routes.each(&:eager_load!) +        nil +      end +        def relative_url_root          @config.relative_url_root        end diff --git a/actionpack/lib/action_dispatch/testing/assertions.rb b/actionpack/lib/action_dispatch/testing/assertions.rb index b362931ef7..4ea18d671d 100644 --- a/actionpack/lib/action_dispatch/testing/assertions.rb +++ b/actionpack/lib/action_dispatch/testing/assertions.rb @@ -12,7 +12,7 @@ module ActionDispatch      include Rails::Dom::Testing::Assertions      def html_document -      @html_document ||= if @response.content_type.to_s =~ /xml\z/ +      @html_document ||= if @response.content_type.to_s.end_with?("xml")          Nokogiri::XML::Document.parse(@response.body)        else          Nokogiri::HTML::Document.parse(@response.body) diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb index 454dcb9307..37c1ca02b6 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb @@ -152,8 +152,11 @@ module ActionDispatch            _routes = @routes            @controller.singleton_class.include(_routes.url_helpers) -          @controller.view_context_class = Class.new(@controller.view_context_class) do -            include _routes.url_helpers + +          if @controller.respond_to? :view_context_class +            @controller.view_context_class = Class.new(@controller.view_context_class) do +              include _routes.url_helpers +            end            end          end          yield @routes diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index 021ffec862..15f816a0ae 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -145,8 +145,8 @@ module ActionDispatch          self.host        = DEFAULT_HOST          self.remote_addr = "127.0.0.1" -        self.accept      = "text/xml,application/xml,application/xhtml+xml," + -                           "text/html;q=0.9,text/plain;q=0.8,image/png," + +        self.accept      = "text/xml,application/xml,application/xhtml+xml," \ +                           "text/html;q=0.9,text/plain;q=0.8,image/png," \                             "*/*;q=0.5"          unless defined? @named_routes_configured diff --git a/actionpack/lib/action_pack.rb b/actionpack/lib/action_pack.rb index ee6dabd133..eec622e085 100644 --- a/actionpack/lib/action_pack.rb +++ b/actionpack/lib/action_pack.rb @@ -1,5 +1,5 @@  #-- -# Copyright (c) 2004-2016 David Heinemeier Hansson +# Copyright (c) 2004-2017 David Heinemeier Hansson  #  # Permission is hereby granted, free of charge, to any person obtaining  # a copy of this software and associated documentation files (the diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index 11a9092527..459b0d6c54 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -7,8 +7,8 @@ require "active_support/core_ext/kernel/reporting"  # These are the normal settings that will be set up by Railties  # TODO: Have these tests support other combinations of these values  silence_warnings do -  Encoding.default_internal = "UTF-8" -  Encoding.default_external = "UTF-8" +  Encoding.default_internal = Encoding::UTF_8 +  Encoding.default_external = Encoding::UTF_8  end  require "drb" @@ -259,9 +259,9 @@ module ActionDispatch        host = uri_or_host.host unless path        path ||= uri_or_host.path -      params = { "PATH_INFO"      => path, -                "REQUEST_METHOD" => method, -                "HTTP_HOST"      => host } +      params = { "PATH_INFO" => path, +                 "REQUEST_METHOD" => method, +                 "HTTP_HOST" => host }        routes.call(params)      end @@ -354,15 +354,6 @@ class CommentsController < ResourcesController; end  class AccountsController < ResourcesController; end  class ImagesController < ResourcesController; end -# Skips the current run on Rubinius using Minitest::Assertions#skip -def rubinius_skip(message = "") -  skip message if RUBY_ENGINE == "rbx" -end -# Skips the current run on JRuby using Minitest::Assertions#skip -def jruby_skip(message = "") -  skip message if defined?(JRUBY_VERSION) -end -  require "active_support/testing/method_call_assertions"  class ForkingExecutor @@ -438,4 +429,13 @@ end  class ActiveSupport::TestCase    include ActiveSupport::Testing::MethodCallAssertions + +  # Skips the current run on Rubinius using Minitest::Assertions#skip +  private def rubinius_skip(message = "") +    skip message if RUBY_ENGINE == "rbx" +  end +  # Skips the current run on JRuby using Minitest::Assertions#skip +  private def jruby_skip(message = "") +    skip message if defined?(JRUBY_VERSION) +  end  end diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb index b08f1f1707..9ab152fc5c 100644 --- a/actionpack/test/controller/action_pack_assertions_test.rb +++ b/actionpack/test/controller/action_pack_assertions_test.rb @@ -128,6 +128,16 @@ module Admin    end  end +class ApiOnlyController < ActionController::API +  def nothing +    head :ok +  end + +  def redirect_to_new_route +    redirect_to new_route_url +  end +end +  class ActionPackAssertionsControllerTest < ActionController::TestCase    def test_render_file_absolute_path      get :render_file_absolute_path @@ -170,6 +180,20 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase      end    end +  def test_with_routing_works_with_api_only_controllers +    @controller = ApiOnlyController.new + +    with_routing do |set| +      set.draw do +        get "new_route", to: "api_only#nothing" +        get "redirect_to_new_route", to: "api_only#redirect_to_new_route" +      end + +      process :redirect_to_new_route +      assert_redirected_to "http://test.host/new_route" +    end +  end +    def test_assert_redirect_to_named_route_failure      with_routing do |set|        set.draw do diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb index f9701585a9..5f1463cfa8 100644 --- a/actionpack/test/controller/filters_test.rb +++ b/actionpack/test/controller/filters_test.rb @@ -584,7 +584,7 @@ class FilterTest < ActionController::TestCase      assert @controller.instance_variable_get(:@was_audited)    end -  def test_running_anomolous_yet_valid_condition_actions +  def test_running_anomalous_yet_valid_condition_actions      test_process(AnomolousYetValidConditionController)      assert_equal %w( ensure_login ), @controller.instance_variable_get(:@ran_filter)      assert @controller.instance_variable_get(:@ran_class_action) @@ -704,7 +704,7 @@ class FilterTest < ActionController::TestCase    def test_prepending_and_appending_around_action      test_process(MixedFilterController) -    assert_equal " before aroundfilter  before procfilter  before appended aroundfilter " + +    assert_equal " before aroundfilter  before procfilter  before appended aroundfilter " \                   " after appended aroundfilter  after procfilter  after aroundfilter ",                   MixedFilterController.execution_log    end diff --git a/actionpack/test/controller/flash_hash_test.rb b/actionpack/test/controller/flash_hash_test.rb index 6b3abdd5be..45b598a594 100644 --- a/actionpack/test/controller/flash_hash_test.rb +++ b/actionpack/test/controller/flash_hash_test.rb @@ -63,10 +63,10 @@ module ActionDispatch        assert_equal({ "flashes" => { "foo" => "bar" }, "discard" => [] }, @hash.to_session_value)        @hash.discard("foo") -      assert_equal(nil, @hash.to_session_value) +      assert_nil(@hash.to_session_value)        @hash.sweep -      assert_equal(nil, @hash.to_session_value) +      assert_nil(@hash.to_session_value)      end      def test_from_session_value @@ -75,7 +75,7 @@ module ActionDispatch        session = Marshal.load(Base64.decode64(rails_3_2_cookie))        hash = Flash::FlashHash.from_session_value(session["flash"])        assert_equal({ "greeting" => "Hello" }, hash.to_hash) -      assert_equal(nil, hash.to_session_value) +      assert_nil(hash.to_session_value)      end      def test_from_session_value_on_json_serializer @@ -84,7 +84,7 @@ module ActionDispatch        hash = Flash::FlashHash.from_session_value(session["flash"])        assert_equal({ "greeting" => "Hello" }, hash.to_hash) -      assert_equal(nil, hash.to_session_value) +      assert_nil(hash.to_session_value)        assert_equal "Hello", hash[:greeting]        assert_equal "Hello", hash["greeting"]      end diff --git a/actionpack/test/controller/http_token_authentication_test.rb b/actionpack/test/controller/http_token_authentication_test.rb index 3842136682..09d2793c9a 100644 --- a/actionpack/test/controller/http_token_authentication_test.rb +++ b/actionpack/test/controller/http_token_authentication_test.rb @@ -166,8 +166,7 @@ class HttpTokenAuthenticationTest < ActionController::TestCase    test "token_and_options returns nil with no value after the equal sign" do      actual = ActionController::HttpAuthentication::Token.token_and_options(malformed_request).first -    expected = nil -    assert_equal(expected, actual) +    assert_nil actual    end    test "raw_params returns a tuple of two key value pair strings" do diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb index 4fae4071b1..57f58fd835 100644 --- a/actionpack/test/controller/integration_test.rb +++ b/actionpack/test/controller/integration_test.rb @@ -269,8 +269,8 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest    test "response cookies are added to the cookie jar for the next request" do      with_test_route_set do -      self.cookies["cookie_1"] = "sugar" -      self.cookies["cookie_2"] = "oatmeal" +      cookies["cookie_1"] = "sugar" +      cookies["cookie_2"] = "oatmeal"        get "/cookie_monster"        assert_equal "cookie_1=; path=/\ncookie_3=chocolate; path=/", headers["Set-Cookie"]        assert_equal({ "cookie_1" => "", "cookie_2" => "oatmeal", "cookie_3" => "chocolate" }, cookies.to_hash) @@ -494,7 +494,7 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest      assert_includes @response.headers, "c"    end -  def test_accept_not_overriden_when_xhr_true +  def test_accept_not_overridden_when_xhr_true      with_test_route_set do        get "/get", headers: { "Accept" => "application/json" }, xhr: true        assert_equal "application/json", request.accept diff --git a/actionpack/test/controller/mime/respond_to_test.rb b/actionpack/test/controller/mime/respond_to_test.rb index 818dc119eb..61bd5c80c4 100644 --- a/actionpack/test/controller/mime/respond_to_test.rb +++ b/actionpack/test/controller/mime/respond_to_test.rb @@ -519,7 +519,7 @@ class RespondToControllerTest < ActionController::TestCase      assert_equal "Whatever you ask for, I got it", @response.body    end -  def test_handle_any_any_unkown_format +  def test_handle_any_any_unknown_format      get :handle_any_any, format: "php"      assert_equal "Whatever you ask for, I got it", @response.body    end diff --git a/actionpack/test/controller/parameters/nested_parameters_permit_test.rb b/actionpack/test/controller/parameters/nested_parameters_permit_test.rb index 5f86901e30..00e591d5a7 100644 --- a/actionpack/test/controller/parameters/nested_parameters_permit_test.rb +++ b/actionpack/test/controller/parameters/nested_parameters_permit_test.rb @@ -140,6 +140,11 @@ class NestedParametersPermitTest < ActiveSupport::TestCase      assert_equal "William Shakespeare", permitted[:book][:authors_attributes]["0"][:name]      assert_equal "Unattributed Assistant", permitted[:book][:authors_attributes]["1"][:name] +    assert_equal( +      { "book" => { "authors_attributes" => { "0" => { "name" => "William Shakespeare" }, "1" => { "name" => "Unattributed Assistant" }, "2" => {} } } }, +      permitted.to_h +    ) +      assert_filtered_out permitted[:book][:authors_attributes]["0"], :age_of_death    end diff --git a/actionpack/test/controller/parameters/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb index b62a3d6d7b..8920914af1 100644 --- a/actionpack/test/controller/parameters/parameters_permit_test.rb +++ b/actionpack/test/controller/parameters/parameters_permit_test.rb @@ -66,12 +66,20 @@ class ParametersPermitTest < ActiveSupport::TestCase      values.each do |value|        params = ActionController::Parameters.new(id: value)        permitted = params.permit(:id) -      assert_equal value, permitted[:id] +      if value.nil? +        assert_nil permitted[:id] +      else +        assert_equal value, permitted[:id] +      end        @struct_fields.each do |sf|          params = ActionController::Parameters.new(sf => value)          permitted = params.permit(:sf) -        assert_equal value, permitted[sf] +        if value.nil? +          assert_nil permitted[sf] +        else +          assert_equal value, permitted[sf] +        end        end      end    end @@ -187,11 +195,6 @@ class ParametersPermitTest < ActiveSupport::TestCase      permitted = params.permit(:username, preferences: {}, hacked: {}) -    assert permitted.permitted? -    assert permitted[:preferences].permitted? -    assert permitted[:preferences][:font].permitted? -    assert permitted[:preferences][:dubious].all?(&:permitted?) -      assert_equal "fxn",             permitted[:username]      assert_equal "Marazul",         permitted[:preferences][:scheme]      assert_equal "Source Code Pro", permitted[:preferences][:font][:name] diff --git a/actionpack/test/controller/params_wrapper_test.rb b/actionpack/test/controller/params_wrapper_test.rb index 46a2ab8ccf..faa57c4559 100644 --- a/actionpack/test/controller/params_wrapper_test.rb +++ b/actionpack/test/controller/params_wrapper_test.rb @@ -50,7 +50,7 @@ class ParamsWrapperTest < ActionController::TestCase      with_default_wrapper_options do        @request.env["CONTENT_TYPE"] = "application/json"        post :parse, params: { "username" => "sikachu" } -      assert_equal @request.filtered_parameters, "controller" => "params_wrapper_test/users", "action" => "parse", "username" => "sikachu", "user" => { "username" => "sikachu" } +      assert_equal({ "controller" => "params_wrapper_test/users", "action" => "parse", "username" => "sikachu", "user" => { "username" => "sikachu" } }, @request.filtered_parameters)      end    end diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index 0c0f18f200..3a0a0a8bde 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -313,7 +313,6 @@ class ExpiresInRenderTest < ActionController::TestCase    end    def test_permitted_dynamic_render_file_hash -    skip "FIXME: this test passes on 4-2-stable but not master. Why?"      assert File.exist?(File.join(File.dirname(__FILE__), "../../test/abstract_unit.rb"))      response = get :dynamic_render_permit, params: { id: { file: '../\\../test/abstract_unit.rb' } }      assert_equal File.read(File.join(File.dirname(__FILE__), "../../test/abstract_unit.rb")), diff --git a/actionpack/test/controller/request/test_request_test.rb b/actionpack/test/controller/request/test_request_test.rb index 49a19df4df..1440db00f6 100644 --- a/actionpack/test/controller/request/test_request_test.rb +++ b/actionpack/test/controller/request/test_request_test.rb @@ -17,20 +17,24 @@ class ActionController::TestRequestTest < ActionController::TestCase      @request.set_header "CONTENT_TYPE", "application/json"      @request.assign_parameters(@routes, "test", "create", non_ascii_parameters,                                 "/test", [:data, :controller, :action]) -    assert_equal(@request.get_header("CONTENT_LENGTH"), -                 StringIO.new(non_ascii_parameters.to_json).length.to_s) +    assert_equal(StringIO.new(non_ascii_parameters.to_json).length.to_s, +                 @request.get_header("CONTENT_LENGTH"))    end -  ActionDispatch::Session::AbstractStore::DEFAULT_OPTIONS.each_key do |option| -    test "rack default session options #{option} exists in session options and is default" do -      assert_equal(ActionDispatch::Session::AbstractStore::DEFAULT_OPTIONS[option], -                   @request.session_options[option], -                   "Missing rack session default option #{option} in request.session_options") +  ActionDispatch::Session::AbstractStore::DEFAULT_OPTIONS.each_pair do |key, value| +    test "rack default session options #{key} exists in session options and is default" do +      if value.nil? +        assert_nil(@request.session_options[key], +                   "Missing rack session default option #{key} in request.session_options") +      else +        assert_equal(value, @request.session_options[key], +                     "Missing rack session default option #{key} in request.session_options") +      end      end -    test "rack default session options #{option} exists in session options" do -      assert(@request.session_options.has_key?(option), -                   "Missing rack session option #{option} in request.session_options") +    test "rack default session options #{key} exists in session options" do +      assert(@request.session_options.has_key?(key), +                   "Missing rack session option #{key} in request.session_options")      end    end  end diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb index 874f9c3c42..ac99830208 100644 --- a/actionpack/test/controller/test_case_test.rb +++ b/actionpack/test/controller/test_case_test.rb @@ -728,6 +728,20 @@ XML      assert_equal "text/html", @response.body    end +  def test_request_path_info_and_format_reset +    get :test_format, format: "json" +    assert_equal "application/json", @response.body + +    get :test_uri, format: "json" +    assert_equal "/test_case_test/test/test_uri.json", @response.body + +    get :test_format +    assert_equal "text/html", @response.body + +    get :test_uri +    assert_equal "/test_case_test/test/test_uri", @response.body +  end +    def test_request_format_kwarg_overrides_params      get :test_format, format: "json", params: { format: "html" }      assert_equal "application/json", @response.body diff --git a/actionpack/test/controller/url_for_test.rb b/actionpack/test/controller/url_for_test.rb index 4b6f33c545..862dcf01c3 100644 --- a/actionpack/test/controller/url_for_test.rb +++ b/actionpack/test/controller/url_for_test.rb @@ -26,7 +26,7 @@ module AbstractController                                               action: :index              }            }.url_helpers -          self.default_url_options[:host] = "example.com" +          default_url_options[:host] = "example.com"          }          path = klass.new.fun_path(controller: :articles, @@ -487,6 +487,27 @@ module AbstractController          end        end +      def test_default_params_first_empty +        with_routing do |set| +          set.draw do +            get "(:param1)/test(/:param2)" => "index#index", +              defaults: { +                param1: 1, +                param2: 2 +              }, +              constraints: { +                param1: /\d*/, +                param2: /\d+/ +              } +          end + +          kls = Class.new { include set.url_helpers } +          kls.default_url_options[:host] = "www.basecamphq.com" + +          assert_equal "http://www.basecamphq.com/test", kls.new.url_for(controller: "index", param1: "1") +        end +      end +        private          def extract_params(url)            url.split("?", 2).last.split("&").sort diff --git a/actionpack/test/dispatch/callbacks_test.rb b/actionpack/test/dispatch/callbacks_test.rb index 57e21a22c6..29a5dfc0ad 100644 --- a/actionpack/test/dispatch/callbacks_test.rb +++ b/actionpack/test/dispatch/callbacks_test.rb @@ -35,24 +35,6 @@ class DispatcherTest < ActiveSupport::TestCase      assert_equal 6, Foo.b    end -  def test_to_prepare_and_cleanup_delegation -    prepared = cleaned = false -    assert_deprecated do -      ActionDispatch::Callbacks.to_prepare { prepared = true } -      ActionDispatch::Callbacks.to_prepare { cleaned = true } -    end - -    assert_deprecated do -      ActionDispatch::Reloader.prepare! -    end -    assert prepared - -    assert_deprecated do -      ActionDispatch::Reloader.cleanup! -    end -    assert cleaned -  end -    private      def dispatch(&block) diff --git a/actionpack/test/dispatch/prefix_generation_test.rb b/actionpack/test/dispatch/prefix_generation_test.rb index bb2fc53add..0e093d2188 100644 --- a/actionpack/test/dispatch/prefix_generation_test.rb +++ b/actionpack/test/dispatch/prefix_generation_test.rb @@ -22,8 +22,6 @@ module TestGenerationPrefix    end    class WithMountedEngine < ActionDispatch::IntegrationTest -    include Rack::Test::Methods -      class BlogEngine < Rails::Engine        routes.draw do          get "/posts/:id", to: "inside_engine_generating#show", as: :post @@ -153,114 +151,114 @@ module TestGenerationPrefix      # Inside Engine      test "[ENGINE] generating engine's url use SCRIPT_NAME from request" do        get "/pure-awesomeness/blog/posts/1" -      assert_equal "/pure-awesomeness/blog/posts/1", last_response.body +      assert_equal "/pure-awesomeness/blog/posts/1", response.body      end      test "[ENGINE] generating application's url never uses SCRIPT_NAME from request" do        get "/pure-awesomeness/blog/url_to_application" -      assert_equal "/generate", last_response.body +      assert_equal "/generate", response.body      end      test "[ENGINE] generating engine's url with polymorphic path" do        get "/pure-awesomeness/blog/polymorphic_path_for_engine" -      assert_equal "/pure-awesomeness/blog/posts/1", last_response.body +      assert_equal "/pure-awesomeness/blog/posts/1", response.body      end      test "[ENGINE] url_helpers from engine have higher priority than application's url_helpers" do        get "/awesome/blog/conflicting_url" -      assert_equal "engine", last_response.body +      assert_equal "engine", response.body      end      test "[ENGINE] relative path root uses SCRIPT_NAME from request" do        get "/awesome/blog/relative_path_root" -      verify_redirect "http://example.org/awesome/blog" +      verify_redirect "http://www.example.com/awesome/blog"      end      test "[ENGINE] relative path redirect uses SCRIPT_NAME from request" do        get "/awesome/blog/relative_path_redirect" -      verify_redirect "http://example.org/awesome/blog/foo" +      verify_redirect "http://www.example.com/awesome/blog/foo"      end      test "[ENGINE] relative option root uses SCRIPT_NAME from request" do        get "/awesome/blog/relative_option_root" -      verify_redirect "http://example.org/awesome/blog" +      verify_redirect "http://www.example.com/awesome/blog"      end      test "[ENGINE] relative option redirect uses SCRIPT_NAME from request" do        get "/awesome/blog/relative_option_redirect" -      verify_redirect "http://example.org/awesome/blog/foo" +      verify_redirect "http://www.example.com/awesome/blog/foo"      end      test "[ENGINE] relative custom root uses SCRIPT_NAME from request" do        get "/awesome/blog/relative_custom_root" -      verify_redirect "http://example.org/awesome/blog" +      verify_redirect "http://www.example.com/awesome/blog"      end      test "[ENGINE] relative custom redirect uses SCRIPT_NAME from request" do        get "/awesome/blog/relative_custom_redirect" -      verify_redirect "http://example.org/awesome/blog/foo" +      verify_redirect "http://www.example.com/awesome/blog/foo"      end      test "[ENGINE] absolute path root doesn't use SCRIPT_NAME from request" do        get "/awesome/blog/absolute_path_root" -      verify_redirect "http://example.org/" +      verify_redirect "http://www.example.com/"      end      test "[ENGINE] absolute path redirect doesn't use SCRIPT_NAME from request" do        get "/awesome/blog/absolute_path_redirect" -      verify_redirect "http://example.org/foo" +      verify_redirect "http://www.example.com/foo"      end      test "[ENGINE] absolute option root doesn't use SCRIPT_NAME from request" do        get "/awesome/blog/absolute_option_root" -      verify_redirect "http://example.org/" +      verify_redirect "http://www.example.com/"      end      test "[ENGINE] absolute option redirect doesn't use SCRIPT_NAME from request" do        get "/awesome/blog/absolute_option_redirect" -      verify_redirect "http://example.org/foo" +      verify_redirect "http://www.example.com/foo"      end      test "[ENGINE] absolute custom root doesn't use SCRIPT_NAME from request" do        get "/awesome/blog/absolute_custom_root" -      verify_redirect "http://example.org/" +      verify_redirect "http://www.example.com/"      end      test "[ENGINE] absolute custom redirect doesn't use SCRIPT_NAME from request" do        get "/awesome/blog/absolute_custom_redirect" -      verify_redirect "http://example.org/foo" +      verify_redirect "http://www.example.com/foo"      end      # Inside Application      test "[APP] generating engine's route includes prefix" do        get "/generate" -      assert_equal "/awesome/blog/posts/1", last_response.body +      assert_equal "/awesome/blog/posts/1", response.body      end      test "[APP] generating engine's route includes default_url_options[:script_name]" do        RailsApplication.routes.default_url_options = { script_name: "/something" }        get "/generate" -      assert_equal "/something/awesome/blog/posts/1", last_response.body +      assert_equal "/something/awesome/blog/posts/1", response.body      end      test "[APP] generating engine's url with polymorphic path" do        get "/polymorphic_path_for_engine" -      assert_equal "/awesome/blog/posts/1", last_response.body +      assert_equal "/awesome/blog/posts/1", response.body      end      test "polymorphic_path_for_app" do        get "/polymorphic_path_for_app" -      assert_equal "/posts/1", last_response.body +      assert_equal "/posts/1", response.body      end      test "[APP] generating engine's url with url_for(@post)" do        get "/polymorphic_with_url_for" -      assert_equal "http://example.org/awesome/blog/posts/1", last_response.body +      assert_equal "http://www.example.com/awesome/blog/posts/1", response.body      end      test "[APP] instance variable with same name as engine" do        get "/ivar_usage" -      assert_equal "/awesome/blog/posts/1", last_response.body +      assert_equal "/awesome/blog/posts/1", response.body      end      # Inside any Object @@ -322,9 +320,9 @@ module TestGenerationPrefix      private        def verify_redirect(url, status = 301) -        assert_equal status, last_response.status -        assert_equal url, last_response.headers["Location"] -        assert_equal expected_redirect_body(url), last_response.body +        assert_equal status, response.status +        assert_equal url, response.headers["Location"] +        assert_equal expected_redirect_body(url), response.body        end        def expected_redirect_body(url) @@ -333,8 +331,6 @@ module TestGenerationPrefix    end    class EngineMountedAtRoot < ActionDispatch::IntegrationTest -    include Rack::Test::Methods -      class BlogEngine        def self.routes          @routes ||= begin @@ -388,74 +384,74 @@ module TestGenerationPrefix      test "generating path inside engine" do        get "/posts/1" -      assert_equal "/posts/1", last_response.body +      assert_equal "/posts/1", response.body      end      test "[ENGINE] relative path root uses SCRIPT_NAME from request" do        get "/relative_path_root" -      verify_redirect "http://example.org/" +      verify_redirect "http://www.example.com/"      end      test "[ENGINE] relative path redirect uses SCRIPT_NAME from request" do        get "/relative_path_redirect" -      verify_redirect "http://example.org/foo" +      verify_redirect "http://www.example.com/foo"      end      test "[ENGINE] relative option root uses SCRIPT_NAME from request" do        get "/relative_option_root" -      verify_redirect "http://example.org/" +      verify_redirect "http://www.example.com/"      end      test "[ENGINE] relative option redirect uses SCRIPT_NAME from request" do        get "/relative_option_redirect" -      verify_redirect "http://example.org/foo" +      verify_redirect "http://www.example.com/foo"      end      test "[ENGINE] relative custom root uses SCRIPT_NAME from request" do        get "/relative_custom_root" -      verify_redirect "http://example.org/" +      verify_redirect "http://www.example.com/"      end      test "[ENGINE] relative custom redirect uses SCRIPT_NAME from request" do        get "/relative_custom_redirect" -      verify_redirect "http://example.org/foo" +      verify_redirect "http://www.example.com/foo"      end      test "[ENGINE] absolute path root doesn't use SCRIPT_NAME from request" do        get "/absolute_path_root" -      verify_redirect "http://example.org/" +      verify_redirect "http://www.example.com/"      end      test "[ENGINE] absolute path redirect doesn't use SCRIPT_NAME from request" do        get "/absolute_path_redirect" -      verify_redirect "http://example.org/foo" +      verify_redirect "http://www.example.com/foo"      end      test "[ENGINE] absolute option root doesn't use SCRIPT_NAME from request" do        get "/absolute_option_root" -      verify_redirect "http://example.org/" +      verify_redirect "http://www.example.com/"      end      test "[ENGINE] absolute option redirect doesn't use SCRIPT_NAME from request" do        get "/absolute_option_redirect" -      verify_redirect "http://example.org/foo" +      verify_redirect "http://www.example.com/foo"      end      test "[ENGINE] absolute custom root doesn't use SCRIPT_NAME from request" do        get "/absolute_custom_root" -      verify_redirect "http://example.org/" +      verify_redirect "http://www.example.com/"      end      test "[ENGINE] absolute custom redirect doesn't use SCRIPT_NAME from request" do        get "/absolute_custom_redirect" -      verify_redirect "http://example.org/foo" +      verify_redirect "http://www.example.com/foo"      end      private        def verify_redirect(url, status = 301) -        assert_equal status, last_response.status -        assert_equal url, last_response.headers["Location"] -        assert_equal expected_redirect_body(url), last_response.body +        assert_equal status, response.status +        assert_equal url, response.headers["Location"] +        assert_equal expected_redirect_body(url), response.body        end        def expected_redirect_body(url) diff --git a/actionpack/test/dispatch/reloader_test.rb b/actionpack/test/dispatch/reloader_test.rb index db68549b84..797430c85a 100644 --- a/actionpack/test/dispatch/reloader_test.rb +++ b/actionpack/test/dispatch/reloader_test.rb @@ -1,32 +1,11 @@  require "abstract_unit"  class ReloaderTest < ActiveSupport::TestCase -  Reloader = ActionDispatch::Reloader -    teardown do      ActiveSupport::Reloader.reset_callbacks :prepare      ActiveSupport::Reloader.reset_callbacks :complete    end -  def test_prepare_callbacks -    a = b = c = nil -    assert_deprecated do -      Reloader.to_prepare { |*args| a = b = c = 1 } -      Reloader.to_prepare { |*args| b = c = 2 } -      Reloader.to_prepare { |*args| c = 3 } -    end - -    # Ensure to_prepare callbacks are not run when defined -    assert_nil a || b || c - -    # Run callbacks -    call_and_return_body - -    assert_equal 1, a -    assert_equal 2, b -    assert_equal 3, c -  end -    class MyBody < Array      def initialize(&block)        @on_close = block @@ -45,6 +24,23 @@ class ReloaderTest < ActiveSupport::TestCase      end    end +  def test_prepare_callbacks +    a = b = c = nil +    reloader.to_prepare { |*args| a = b = c = 1 } +    reloader.to_prepare { |*args| b = c = 2 } +    reloader.to_prepare { |*args| c = 3 } + +    # Ensure to_prepare callbacks are not run when defined +    assert_nil a || b || c + +    # Run callbacks +    call_and_return_body + +    assert_equal 1, a +    assert_equal 2, b +    assert_equal 3, c +  end +    def test_returned_body_object_always_responds_to_close      body = call_and_return_body      assert_respond_to body, :close @@ -62,15 +58,12 @@ class ReloaderTest < ActiveSupport::TestCase    def test_condition_specifies_when_to_reload      i, j = 0, 0, 0, 0 -    assert_deprecated do -      Reloader.to_prepare { |*args| i += 1 } -      Reloader.to_cleanup { |*args| j += 1 } -    end -    x = Class.new(ActiveSupport::Reloader) -    x.check = lambda { i < 3 } +    reloader = reloader(lambda { i < 3 }) +    reloader.to_prepare { |*args| i += 1 } +    reloader.to_complete { |*args| j += 1 } -    app = Reloader.new(lambda { |env| [200, {}, []] }, x) +    app = middleware(lambda { |env| [200, {}, []] }, reloader)      5.times do        resp = app.call({})        resp[2].close @@ -115,24 +108,20 @@ class ReloaderTest < ActiveSupport::TestCase      assert_respond_to body, :bar    end -  def test_cleanup_callbacks_are_called_when_body_is_closed -    cleaned = false -    assert_deprecated do -      Reloader.to_cleanup { cleaned = true } -    end +  def test_complete_callbacks_are_called_when_body_is_closed +    completed = false +    reloader.to_complete { completed = true }      body = call_and_return_body -    assert !cleaned +    assert !completed      body.close -    assert cleaned +    assert completed    end    def test_prepare_callbacks_arent_called_when_body_is_closed      prepared = false -    assert_deprecated do -      Reloader.to_prepare { prepared = true } -    end +    reloader.to_prepare { prepared = true }      body = call_and_return_body      prepared = false @@ -141,45 +130,9 @@ class ReloaderTest < ActiveSupport::TestCase      assert !prepared    end -  def test_manual_reloading -    prepared = cleaned = false -    assert_deprecated do -      Reloader.to_prepare { prepared = true } -      Reloader.to_cleanup { cleaned  = true } -    end - -    assert_deprecated do -      Reloader.prepare! -    end -    assert prepared -    assert !cleaned - -    prepared = cleaned = false -    assert_deprecated do -      Reloader.cleanup! -    end -    assert prepared -    assert cleaned -  end - -  def test_prepend_prepare_callback -    i = 10 -    assert_deprecated do -      Reloader.to_prepare { i += 1 } -      Reloader.to_prepare(prepend: true) { i = 0 } -    end - -    assert_deprecated do -      Reloader.prepare! -    end -    assert_equal 1, i -  end - -  def test_cleanup_callbacks_are_called_on_exceptions -    cleaned = false -    assert_deprecated do -      Reloader.to_cleanup { cleaned = true } -    end +  def test_complete_callbacks_are_called_on_exceptions +    completed = false +    reloader.to_complete { completed = true }      begin        call_and_return_body do @@ -188,16 +141,26 @@ class ReloaderTest < ActiveSupport::TestCase      rescue      end -    assert cleaned +    assert completed    end    private      def call_and_return_body(&block) -      x = Class.new(ActiveSupport::Reloader) -      x.check = lambda { true } +      app = middleware(block || proc { [200, {}, "response"] }) +      _, _, body = app.call("rack.input" => StringIO.new("")) +      body +    end + +    def middleware(inner_app, reloader = reloader()) +      ActionDispatch::Reloader.new(inner_app, reloader) +    end + +    def reloader(check = lambda { true }) +      @reloader ||= begin +                      reloader = Class.new(ActiveSupport::Reloader) +                      reloader.check = check +                      reloader +                    end -      @response ||= "response" -      @reloader ||= Reloader.new(block || proc { [200, {}, @response] }, x) -      @reloader.call("rack.input" => StringIO.new(""))[2]      end  end diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index e11b93b4f0..2f9228a62d 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -760,8 +760,8 @@ class RequestMethod < BaseRequestTest    end    test "post uneffected by local inflections" do -    existing_acrnoyms = ActiveSupport::Inflector.inflections.acronyms.dup -    existing_acrnoym_regex = ActiveSupport::Inflector.inflections.acronym_regex.dup +    existing_acronyms = ActiveSupport::Inflector.inflections.acronyms.dup +    existing_acronym_regex = ActiveSupport::Inflector.inflections.acronym_regex.dup      begin        ActiveSupport::Inflector.inflections do |inflect|          inflect.acronym "POS" @@ -774,8 +774,8 @@ class RequestMethod < BaseRequestTest      ensure        # Reset original acronym set        ActiveSupport::Inflector.inflections do |inflect| -        inflect.send(:instance_variable_set, "@acronyms", existing_acrnoyms) -        inflect.send(:instance_variable_set, "@acronym_regex", existing_acrnoym_regex) +        inflect.send(:instance_variable_set, "@acronyms", existing_acronyms) +        inflect.send(:instance_variable_set, "@acronym_regex", existing_acronym_regex)        end      end    end diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb index 3cf7f4821d..7433c5ce0c 100644 --- a/actionpack/test/dispatch/response_test.rb +++ b/actionpack/test/dispatch/response_test.rb @@ -111,7 +111,7 @@ class ResponseTest < ActiveSupport::TestCase    end    def test_empty_content_type_returns_nil -    @response.headers['Content-Type'] = "" +    @response.headers["Content-Type"] = ""      assert_nil @response.content_type    end diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index 474d053af6..53758a4fbc 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -1603,7 +1603,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest        get "account/login", to: redirect("/login")      end -    previous_host, self.host = self.host, "www.example.com:3000" +    previous_host, self.host = host, "www.example.com:3000"      get "/account/login"      verify_redirect "http://www.example.com:3000/login" diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb index 2a1053be16..63dfc07c0d 100644 --- a/actionpack/test/dispatch/session/cookie_store_test.rb +++ b/actionpack/test/dispatch/session/cookie_store_test.rb @@ -175,7 +175,7 @@ class CookieStoreTest < ActionDispatch::IntegrationTest    def test_doesnt_write_session_cookie_if_session_is_unchanged      with_test_route_set do -      cookies[SessionKey] = "BAh7BjoIZm9vIghiYXI%3D--" + +      cookies[SessionKey] = "BAh7BjoIZm9vIghiYXI%3D--" \          "fef868465920f415f2c0652d6910d3af288a0367"        get "/no_session_access"        assert_response :success diff --git a/actionpack/test/dispatch/ssl_test.rb b/actionpack/test/dispatch/ssl_test.rb index e29ffa750c..757e26973f 100644 --- a/actionpack/test/dispatch/ssl_test.rb +++ b/actionpack/test/dispatch/ssl_test.rb @@ -102,7 +102,11 @@ class StrictTransportSecurityTest < SSLTest    def assert_hsts(expected, url: "https://example.org", hsts: { subdomains: true }, headers: {})      self.app = build_app ssl_options: { hsts: hsts }, headers: headers      get url -    assert_equal expected, response.headers["Strict-Transport-Security"] +    if expected.nil? +      assert_nil response.headers["Strict-Transport-Security"] +    else +      assert_equal expected, response.headers["Strict-Transport-Security"] +    end    end    test "enabled by default" do @@ -130,7 +134,7 @@ class StrictTransportSecurityTest < SSLTest    end    test ":expires supports AS::Duration arguments" do -    assert_hsts "max-age=31557600; includeSubDomains", hsts: { expires: 1.year } +    assert_hsts "max-age=31556952; includeSubDomains", hsts: { expires: 1.year }    end    test "include subdomains" do diff --git a/actionpack/test/dispatch/static_test.rb b/actionpack/test/dispatch/static_test.rb index d8bc96e3e0..bd8318f5f6 100644 --- a/actionpack/test/dispatch/static_test.rb +++ b/actionpack/test/dispatch/static_test.rb @@ -163,7 +163,7 @@ module StaticTests      assert_equal file_name, env["PATH_INFO"]    end -  def test_serves_gzip_with_propper_content_type_fallback +  def test_serves_gzip_with_proper_content_type_fallback      file_name = "/gzip/foo.zoo"      response  = get(file_name, "HTTP_ACCEPT_ENCODING" => "gzip")      assert_gzip file_name, response diff --git a/actionpack/test/journey/path/pattern_test.rb b/actionpack/test/journey/path/pattern_test.rb index d61a8c023a..2c74617944 100644 --- a/actionpack/test/journey/path/pattern_test.rb +++ b/actionpack/test/journey/path/pattern_test.rb @@ -20,7 +20,7 @@ module ActionDispatch            "/:controller/*foo/bar"        => %r{\A/(#{x})/(.+)/bar\Z},            "/:foo|*bar"                   => %r{\A/(?:([^/.?]+)|(.+))\Z},          }.each do |path, expected| -          define_method(:"test_to_regexp_#{path}") do +          define_method(:"test_to_regexp_#{Regexp.escape(path)}") do              path = Pattern.build(                path,                { controller: /.+/ }, @@ -44,7 +44,7 @@ module ActionDispatch            "/:controller/*foo/bar"        => %r{\A/(#{x})/(.+)/bar},            "/:foo|*bar"                   => %r{\A/(?:([^/.?]+)|(.+))},          }.each do |path, expected| -          define_method(:"test_to_non_anchored_regexp_#{path}") do +          define_method(:"test_to_non_anchored_regexp_#{Regexp.escape(path)}") do              path = Pattern.build(                path,                { controller: /.+/ }, @@ -67,7 +67,7 @@ module ActionDispatch            "/:controller/*foo"            => %w{ controller foo },            "/:controller/*foo/bar"        => %w{ controller foo },          }.each do |path, expected| -          define_method(:"test_names_#{path}") do +          define_method(:"test_names_#{Regexp.escape(path)}") do              path = Pattern.build(                path,                { controller: /.+/ }, diff --git a/actionpack/test/journey/route_test.rb b/actionpack/test/journey/route_test.rb index d2a8163ffb..8fd73970b8 100644 --- a/actionpack/test/journey/route_test.rb +++ b/actionpack/test/journey/route_test.rb @@ -96,7 +96,7 @@ module ActionDispatch          path = Path::Pattern.from_string "/:controller(/:action(/:id))(.:format)"          generic = Route.build "name", nil, path, constraints, [], {} -        knowledge = { id: 20, controller: "pages", action: "show" } +        knowledge = { "id" => true, "controller" => true, "action" => true }          routes = [specific, generic] diff --git a/actionpack/test/lib/controller/fake_models.rb b/actionpack/test/lib/controller/fake_models.rb index 046b4167bb..b768553e7a 100644 --- a/actionpack/test/lib/controller/fake_models.rb +++ b/actionpack/test/lib/controller/fake_models.rb @@ -1,6 +1,6 @@  require "active_model" -class Customer < Struct.new(:name, :id) +Customer = Struct.new(:name, :id) do    extend ActiveModel::Naming    include ActiveModel::Conversion @@ -28,7 +28,7 @@ class Customer < Struct.new(:name, :id)    end  end -class Post < Struct.new(:title, :author_name, :body, :secret, :persisted, :written_on, :cost) +Post = Struct.new(:title, :author_name, :body, :secret, :persisted, :written_on, :cost) do    extend ActiveModel::Naming    include ActiveModel::Conversion    extend ActiveModel::Translation diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md index 8da2e4ae1d..b071b260c9 100644 --- a/actionview/CHANGELOG.md +++ b/actionview/CHANGELOG.md @@ -1,3 +1,33 @@ +*   Change the ERB handler from Erubis to Erubi. + +    Erubi is an Erubis fork that's svelte, simple, and currently maintained. +    Plus it supports `--enable-frozen-string-literal` in Ruby 2.3+. + +    Compatibility: Drops support for `<%===` tags for debug output. +    These were an unused, undocumented side effect of the Erubis +    implementation. + +    Deprecation: The Erubis handler will be removed in Rails 5.2, for the +    handful of folks using it directly. + +    *Jeremy Evans* + +*   Allow render locals to be assigned to instance variables in a view. + +    Fixes #27480. + +    *Andrew White* + +*   Add `check_parameters` option to `current_page?` which makes it more strict. + +    *Maksym Pugach* + +*   Return correct object name in form helper method after `fields_for`. + +    Fixes #26931. + +    *Yuji Yaginuma* +  *   Use `ActionView::Resolver.caching?` (`config.action_view.cache_template_loading`)      to enable template recompilation. diff --git a/actionview/MIT-LICENSE b/actionview/MIT-LICENSE index 8573eb1225..ac810e86d0 100644 --- a/actionview/MIT-LICENSE +++ b/actionview/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2016 David Heinemeier Hansson +Copyright (c) 2004-2017 David Heinemeier Hansson  Permission is hereby granted, free of charge, to any person obtaining  a copy of this software and associated documentation files (the diff --git a/actionview/Rakefile b/actionview/Rakefile index 48f17062ce..cba4684076 100644 --- a/actionview/Rakefile +++ b/actionview/Rakefile @@ -49,7 +49,7 @@ end  namespace :ujs do    desc "Starts the test server"    task :server do -    system 'bundle exec rackup test/ujs/config.ru -p 4567 -s puma' +    system "bundle exec rackup test/ujs/config.ru -p 4567 -s puma"    end  end diff --git a/actionview/actionview.gemspec b/actionview/actionview.gemspec index 7bfdfbe29a..cfaa5007a1 100644 --- a/actionview/actionview.gemspec +++ b/actionview/actionview.gemspec @@ -22,8 +22,8 @@ Gem::Specification.new do |s|    s.add_dependency "activesupport", version    s.add_dependency "builder",       "~> 3.1" -  s.add_dependency "erubis",        "~> 2.7.0" -  s.add_dependency "rails-html-sanitizer", "~> 1.0", ">= 1.0.2" +  s.add_dependency "erubi",         "~> 1.4" +  s.add_dependency "rails-html-sanitizer", "~> 1.0", ">= 1.0.3"    s.add_dependency "rails-dom-testing", "~> 2.0"    s.add_development_dependency "actionpack",  version diff --git a/actionview/app/assets/javascripts/utils/event.coffee b/actionview/app/assets/javascripts/utils/event.coffee index 049b2a3ecd..d25fe8e546 100644 --- a/actionview/app/assets/javascripts/utils/event.coffee +++ b/actionview/app/assets/javascripts/utils/event.coffee @@ -13,7 +13,7 @@ if typeof CustomEvent is 'function'      evt    CustomEvent.prototype = window.Event.prototype -# Triggers an custom event on an element and returns false if the event result is false +# Triggers a custom event on an element and returns false if the event result is false  fire = Rails.fire = (obj, name, data) ->    event = new CustomEvent(      name, diff --git a/actionview/lib/action_view.rb b/actionview/lib/action_view.rb index ba6755be82..ca586f1d52 100644 --- a/actionview/lib/action_view.rb +++ b/actionview/lib/action_view.rb @@ -1,5 +1,5 @@  #-- -# Copyright (c) 2004-2016 David Heinemeier Hansson +# Copyright (c) 2004-2017 David Heinemeier Hansson  #  # Permission is hereby granted, free of charge, to any person obtaining  # a copy of this software and associated documentation files (the diff --git a/actionview/lib/action_view/base.rb b/actionview/lib/action_view/base.rb index b7c05fdb88..5387174467 100644 --- a/actionview/lib/action_view/base.rb +++ b/actionview/lib/action_view/base.rb @@ -11,7 +11,7 @@ module ActionView #:nodoc:    # = Action View Base    #    # Action View templates can be written in several ways. -  # If the template file has a <tt>.erb</tt> extension, then it uses the erubis[https://rubygems.org/gems/erubis] +  # If the template file has a <tt>.erb</tt> extension, then it uses the erubi[https://rubygems.org/gems/erubi]    # template system which can embed Ruby into an HTML document.    # If the template file has a <tt>.builder</tt> extension, then Jim Weirich's Builder::XmlMarkup library is used.    # diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb index 72a094c629..750f96f29e 100644 --- a/actionview/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb @@ -40,7 +40,7 @@ module ActionView        # When the last parameter is a hash you can add HTML attributes using that        # parameter. The following options are supported:        # -      # * <tt>:extname</tt>  - Append a extention to the generated url unless the extension +      # * <tt>:extname</tt>  - Append an extension to the generated url unless the extension        #   already exists. This only applies for relative urls.        # * <tt>:protocol</tt>  - Sets the protocol of the generated url, this option only        #   applies when a relative url and +host+ options are provided. diff --git a/actionview/lib/action_view/helpers/asset_url_helper.rb b/actionview/lib/action_view/helpers/asset_url_helper.rb index c6a5e04aba..03bd1eb008 100644 --- a/actionview/lib/action_view/helpers/asset_url_helper.rb +++ b/actionview/lib/action_view/helpers/asset_url_helper.rb @@ -237,7 +237,11 @@ module ActionView        def compute_asset_extname(source, options = {})          return if options[:extname] == false          extname = options[:extname] || ASSET_EXTENSIONS[options[:type]] -        extname if extname && File.extname(source) != extname +        if extname && File.extname(source) != extname +          extname +        else +          nil +        end        end        # Maps asset types to public directory. @@ -406,7 +410,7 @@ module ActionView        def video_url(source, options = {})          url_to_asset(source, { type: :video }.merge!(options))        end -      alias_method :url_to_video, :video_url # aliased to avoid conflicts with an video_url named route +      alias_method :url_to_video, :video_url # aliased to avoid conflicts with a video_url named route        # Computes the path to an audio asset in the public audios directory.        # Full paths from the document root will be passed through. @@ -445,7 +449,7 @@ module ActionView        def font_path(source, options = {})          path_to_asset(source, { type: :font }.merge!(options))        end -      alias_method :path_to_font, :font_path # aliased to avoid conflicts with an font_path named route +      alias_method :path_to_font, :font_path # aliased to avoid conflicts with a font_path named route        # Computes the full URL to a font asset.        # This will use +font_path+ internally, so most of their behaviors will be the same. @@ -457,7 +461,7 @@ module ActionView        def font_url(source, options = {})          url_to_asset(source, { type: :font }.merge!(options))        end -      alias_method :url_to_font, :font_url # aliased to avoid conflicts with an font_url named route +      alias_method :url_to_font, :font_url # aliased to avoid conflicts with a font_url named route      end    end  end diff --git a/actionview/lib/action_view/helpers/cache_helper.rb b/actionview/lib/action_view/helpers/cache_helper.rb index 7fdf0fd0e1..15ab7e304f 100644 --- a/actionview/lib/action_view/helpers/cache_helper.rb +++ b/actionview/lib/action_view/helpers/cache_helper.rb @@ -241,8 +241,6 @@ module ActionView        end        def write_fragment_for(name, options) -        # VIEW TODO: Make #capture usable outside of ERB -        # This dance is needed because Builder can't use capture          pos = output_buffer.length          yield          output_safe = output_buffer.html_safe? diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb index a66a56d441..26a625e4fe 100644 --- a/actionview/lib/action_view/helpers/form_helper.rb +++ b/actionview/lib/action_view/helpers/form_helper.rb @@ -1924,10 +1924,11 @@ module ActionView            record_name   = model_name_from_record_or_class(record_object).param_key          end +        object_name = @object_name          index = if options.has_key?(:index)            options[:index]          elsif defined?(@auto_index) -          self.object_name = @object_name.to_s.sub(/\[\]$/, "") +          object_name = object_name.to_s.sub(/\[\]$/, "")            @auto_index          end diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb index ffc52569f2..ffc64e7118 100644 --- a/actionview/lib/action_view/helpers/form_tag_helper.rb +++ b/actionview/lib/action_view/helpers/form_tag_helper.rb @@ -442,21 +442,9 @@ module ActionView        #   # => <input name='commit' type='submit' value='Save' data-disable-with="Save" data-confirm="Are you sure?" />        #        def submit_tag(value = "Save changes", options = {}) -        options = options.stringify_keys +        options = options.deep_stringify_keys          tag_options = { "type" => "submit", "name" => "commit", "value" => value }.update(options) - -        if ActionView::Base.automatically_disable_submit_tag -          unless tag_options["data-disable-with"] == false || (tag_options["data"] && tag_options["data"][:disable_with] == false) -            disable_with_text = tag_options["data-disable-with"] -            disable_with_text ||= tag_options["data"][:disable_with] if tag_options["data"] -            disable_with_text ||= value.to_s.clone -            tag_options.deep_merge!("data" => { "disable_with" => disable_with_text }) -          else -            tag_options["data"].delete(:disable_with) if tag_options["data"] -          end -          tag_options.delete("data-disable-with") -        end - +        set_default_disable_with value, tag_options          tag :input, tag_options        end @@ -898,6 +886,22 @@ module ActionView          def sanitize_to_id(name)            name.to_s.delete("]").tr("^-a-zA-Z0-9:.", "_")          end + +        def set_default_disable_with(value, tag_options) +          return unless ActionView::Base.automatically_disable_submit_tag +          data = tag_options["data"] + +          unless tag_options["data-disable-with"] == false || (data && data["disable_with"] == false) +            disable_with_text = tag_options["data-disable-with"] +            disable_with_text ||= data["disable_with"] if data +            disable_with_text ||= value.to_s.clone +            tag_options.deep_merge!("data" => { "disable_with" => disable_with_text }) +          else +            data.delete("disable_with") if data +          end + +          tag_options.delete("data-disable-with") +        end      end    end  end diff --git a/actionview/lib/action_view/helpers/number_helper.rb b/actionview/lib/action_view/helpers/number_helper.rb index 9e80f0b2ee..b6bc5f4f6f 100644 --- a/actionview/lib/action_view/helpers/number_helper.rb +++ b/actionview/lib/action_view/helpers/number_helper.rb @@ -92,7 +92,7 @@ module ActionView        #   (defaults to "%u%n").  Fields are <tt>%u</tt> for the        #   currency, and <tt>%n</tt> for the number.        # * <tt>:negative_format</tt> - Sets the format for negative -      #   numbers (defaults to prepending an hyphen to the formatted +      #   numbers (defaults to prepending a hyphen to the formatted        #   number given by <tt>:format</tt>).  Accepts the same fields        #   than <tt>:format</tt>, except <tt>%n</tt> is here the        #   absolute value of the number. diff --git a/actionview/lib/action_view/helpers/output_safety_helper.rb b/actionview/lib/action_view/helpers/output_safety_helper.rb index 9f1a890f6a..25defd1276 100644 --- a/actionview/lib/action_view/helpers/output_safety_helper.rb +++ b/actionview/lib/action_view/helpers/output_safety_helper.rb @@ -25,7 +25,7 @@ module ActionView #:nodoc:        #   safe_join([raw("<p>foo</p>"), "<p>bar</p>"], "<br />")        #   # => "<p>foo</p><br /><p>bar</p>"        # -      #   safe_join([raw("<p>foo</p>"), raw("<p>bar</p>")], raw("<br />") +      #   safe_join([raw("<p>foo</p>"), raw("<p>bar</p>")], raw("<br />"))        #   # => "<p>foo</p><br /><p>bar</p>"        #        def safe_join(array, sep = $,) @@ -60,7 +60,7 @@ module ActionView #:nodoc:          when 2            safe_join([array[0], array[1]], options[:two_words_connector])          else -          safe_join([safe_join(array[0...-1], options[:words_connector]), options[:last_word_connector], array[-1]]) +          safe_join([safe_join(array[0...-1], options[:words_connector]), options[:last_word_connector], array[-1]], nil)          end        end      end diff --git a/actionview/lib/action_view/helpers/sanitize_helper.rb b/actionview/lib/action_view/helpers/sanitize_helper.rb index 3d6ff598ee..1e9b813d3d 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper.rb @@ -45,17 +45,15 @@ module ActionView        # Providing a custom Rails::Html scrubber:        #        #   class CommentScrubber < Rails::Html::PermitScrubber -      #     def allowed_node?(node) -      #       !%w(form script comment blockquote).include?(node.name) +      #     def initialize +      #       super +      #       self.tags = %w( form script comment blockquote ) +      #       self.attributes = %w( style )        #     end        #        #     def skip_node?(node)        #       node.text?        #     end -      # -      #     def scrub_attribute?(name) -      #       name == 'style' -      #     end        #   end        #        #   <%= sanitize @comment.body, scrubber: CommentScrubber.new %> diff --git a/actionview/lib/action_view/helpers/tags/base.rb b/actionview/lib/action_view/helpers/tags/base.rb index 74d6324771..0895533a60 100644 --- a/actionview/lib/action_view/helpers/tags/base.rb +++ b/actionview/lib/action_view/helpers/tags/base.rb @@ -16,7 +16,14 @@ module ActionView            @skip_default_ids = options.delete(:skip_default_ids)            @allow_method_names_outside_object = options.delete(:allow_method_names_outside_object)            @options = options -          @auto_index = Regexp.last_match ? retrieve_autoindex(Regexp.last_match.pre_match) : nil + +          if Regexp.last_match +            @generate_indexed_names = true +            @auto_index = retrieve_autoindex(Regexp.last_match.pre_match) +          else +            @generate_indexed_names = false +            @auto_index = nil +          end          end          # This is what child classes implement. @@ -167,7 +174,11 @@ module ActionView            end            def name_and_id_index(options) -            options.key?("index") ? options.delete("index") || "" : @auto_index +            if options.key?("index") +              options.delete("index") || "" +            elsif @generate_indexed_names +              @auto_index || "" +            end            end            def skip_default_ids? diff --git a/actionview/lib/action_view/helpers/tags/date_select.rb b/actionview/lib/action_view/helpers/tags/date_select.rb index 006605885a..638c134deb 100644 --- a/actionview/lib/action_view/helpers/tags/date_select.rb +++ b/actionview/lib/action_view/helpers/tags/date_select.rb @@ -16,7 +16,7 @@ module ActionView          class << self            def select_type -            @select_type ||= self.name.split("::").last.sub("Select", "").downcase +            @select_type ||= name.split("::").last.sub("Select", "").downcase            end          end diff --git a/actionview/lib/action_view/helpers/tags/text_field.rb b/actionview/lib/action_view/helpers/tags/text_field.rb index 4306c3543d..613cade7b3 100644 --- a/actionview/lib/action_view/helpers/tags/text_field.rb +++ b/actionview/lib/action_view/helpers/tags/text_field.rb @@ -17,7 +17,7 @@ module ActionView          class << self            def field_type -            @field_type ||= self.name.split("::").last.sub("Field", "").downcase +            @field_type ||= name.split("::").last.sub("Field", "").downcase            end          end diff --git a/actionview/lib/action_view/helpers/text_helper.rb b/actionview/lib/action_view/helpers/text_helper.rb index 07dccf5b41..bc922f9ce8 100644 --- a/actionview/lib/action_view/helpers/text_helper.rb +++ b/actionview/lib/action_view/helpers/text_helper.rb @@ -187,7 +187,7 @@ module ActionView          unless separator.empty?            text.split(separator).each do |value|              if value.match(regex) -              regex = phrase = value +              phrase = value                break              end            end diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb index 58a4a04dcb..a306903c60 100644 --- a/actionview/lib/action_view/helpers/url_helper.rb +++ b/actionview/lib/action_view/helpers/url_helper.rb @@ -35,7 +35,7 @@ module ActionView          when :back            _back_url          else -          raise ArgumentError, "arguments passed to url_for can't be handled. Please require " + +          raise ArgumentError, "arguments passed to url_for can't be handled. Please require " \                                 "routes or provide your own implementation"          end        end @@ -517,6 +517,9 @@ module ActionView        #   current_page?('http://www.example.com/shop/checkout')        #   # => true        # +      #   current_page?('http://www.example.com/shop/checkout', check_parameters: true) +      #   # => false +      #        #   current_page?('/shop/checkout')        #   # => true        # @@ -530,7 +533,7 @@ module ActionView        #        # We can also pass in the symbol arguments instead of strings.        # -      def current_page?(options) +      def current_page?(options, check_parameters: false)          unless request            raise "You cannot use helpers that need to determine the current " \                  "page unless your view context provides a Request object " \ @@ -539,12 +542,14 @@ module ActionView          return false unless request.get? || request.head? +        check_parameters ||= !options.is_a?(String) && options.try(:delete, :check_parameters)          url_string = URI.parser.unescape(url_for(options)).force_encoding(Encoding::BINARY)          # We ignore any extra parameters in the request_uri if the          # submitted url doesn't have any either. This lets the function          # work with things like ?order=asc -        request_uri = url_string.index("?") ? request.fullpath : request.path +        # the behaviour can be disabled with check_parameters: true +        request_uri = url_string.index("?") || check_parameters ? request.fullpath : request.path          request_uri = URI.parser.unescape(request_uri).force_encoding(Encoding::BINARY)          url_string.chomp!("/") if url_string.start_with?("/") && url_string != "/" diff --git a/actionview/lib/action_view/layouts.rb b/actionview/lib/action_view/layouts.rb index 18e395a67f..81feb90486 100644 --- a/actionview/lib/action_view/layouts.rb +++ b/actionview/lib/action_view/layouts.rb @@ -319,7 +319,7 @@ module ActionView              name_clause            end -        self.class_eval <<-RUBY, __FILE__, __LINE__ + 1 +        class_eval <<-RUBY, __FILE__, __LINE__ + 1            def _layout(formats)              if _conditional_layout?                #{layout_definition} diff --git a/actionview/lib/action_view/renderer/partial_renderer.rb b/actionview/lib/action_view/renderer/partial_renderer.rb index b8a79da97f..647b15ea94 100644 --- a/actionview/lib/action_view/renderer/partial_renderer.rb +++ b/actionview/lib/action_view/renderer/partial_renderer.rb @@ -99,7 +99,7 @@ module ActionView    #   <%= render partial: "ad", collection: @advertisements, spacer_template: "ad_divider" %>    #    # If the given <tt>:collection</tt> is +nil+ or empty, <tt>render</tt> will return nil. This will allow you -  # to specify a text which will displayed instead by using this form: +  # to specify a text which will be displayed instead by using this form:    #    #   <%= render(partial: "ad", collection: @advertisements) || "There's no ad to be displayed" %>    # @@ -458,7 +458,7 @@ module ActionView            locals[counter]   = index            locals[iteration] = partial_iteration -          template = (cache[path] ||= find_template(path, keys + [as, counter])) +          template = (cache[path] ||= find_template(path, keys + [as, counter, iteration]))            content = template.render(view, locals)            partial_iteration.iterate!            content @@ -532,11 +532,11 @@ module ActionView          [variable, variable_counter, variable_iteration]        end -      IDENTIFIER_ERROR_MESSAGE = "The partial name (%s) is not a valid Ruby identifier; " + +      IDENTIFIER_ERROR_MESSAGE = "The partial name (%s) is not a valid Ruby identifier; " \                                   "make sure your partial name starts with underscore." -      OPTION_AS_ERROR_MESSAGE  = "The value (%s) of the option `as` is not a valid Ruby identifier; " + -                                 "make sure it starts with lowercase letter, " + +      OPTION_AS_ERROR_MESSAGE  = "The value (%s) of the option `as` is not a valid Ruby identifier; " \ +                                 "make sure it starts with lowercase letter, " \                                   "and is followed by any combination of letters, numbers and underscores."        def raise_invalid_identifier(path) diff --git a/actionview/lib/action_view/renderer/streaming_template_renderer.rb b/actionview/lib/action_view/renderer/streaming_template_renderer.rb index 7ede034492..62ce985243 100644 --- a/actionview/lib/action_view/renderer/streaming_template_renderer.rb +++ b/actionview/lib/action_view/renderer/streaming_template_renderer.rb @@ -4,7 +4,6 @@ module ActionView    # == TODO    #    # * Support streaming from child templates, partials and so on. -  # * Integrate exceptions with exceptron    # * Rack::Cache needs to support streaming bodies    class StreamingTemplateRenderer < TemplateRenderer #:nodoc:      # A valid Rack::Body (i.e. it responds to each). @@ -28,7 +27,6 @@ module ActionView        private          # This is the same logging logic as in ShowExceptions middleware. -        # TODO Once "exceptron" is in, refactor this piece to simply re-use exceptron.          def log_error(exception)            logger = ActionView::Base.logger            return unless logger diff --git a/actionview/lib/action_view/rendering.rb b/actionview/lib/action_view/rendering.rb index 0e72316eb7..cf18562c45 100644 --- a/actionview/lib/action_view/rendering.rb +++ b/actionview/lib/action_view/rendering.rb @@ -124,7 +124,11 @@ module ActionView            key = action.include?(?/) ? :template : :action            options[key] = action          else -          options[:partial] = action +          if action.respond_to?(:permitted?) && action.permitted? +            options = action +          else +            options[:partial] = action +          end          end          options diff --git a/actionview/lib/action_view/routing_url_for.rb b/actionview/lib/action_view/routing_url_for.rb index f6bd290bf4..687ba7c1b4 100644 --- a/actionview/lib/action_view/routing_url_for.rb +++ b/actionview/lib/action_view/routing_url_for.rb @@ -123,14 +123,14 @@ module ActionView      end      private -    def _routes_context -      controller -    end +      def _routes_context +        controller +      end -    def optimize_routes_generation? -      controller.respond_to?(:optimize_routes_generation?, true) ? -        controller.optimize_routes_generation? : super -    end +      def optimize_routes_generation? +        controller.respond_to?(:optimize_routes_generation?, true) ? +          controller.optimize_routes_generation? : super +      end        def _generate_paths_by_default          true diff --git a/actionview/lib/action_view/template.rb b/actionview/lib/action_view/template.rb index d56239789d..c067031d2d 100644 --- a/actionview/lib/action_view/template.rb +++ b/actionview/lib/action_view/template.rb @@ -140,7 +140,7 @@ module ActionView      end      # Returns whether the underlying handler supports streaming. If so, -    # a streaming buffer *may* be passed when it start rendering. +    # a streaming buffer *may* be passed when it starts rendering.      def supports_streaming?        handler.respond_to?(:supports_streaming?) && handler.supports_streaming?      end @@ -326,7 +326,7 @@ module ActionView          # Only locals with valid variable names get set directly. Others will          # still be available in local_assigns.          locals = @locals - Module::RUBY_RESERVED_KEYWORDS -        locals = locals.grep(/\A(?![A-Z0-9])(?:[[:alnum:]_]|[^\0-\177])+\z/) +        locals = locals.grep(/\A@?(?![A-Z0-9])(?:[[:alnum:]_]|[^\0-\177])+\z/)          # Double assign to suppress the dreaded 'assigned but unused variable' warning          locals.each_with_object("") { |key, code| code << "#{key} = #{key} = local_assigns[:#{key}];" } diff --git a/actionview/lib/action_view/template/handlers/builder.rb b/actionview/lib/action_view/template/handlers/builder.rb index 494b543152..e99b921cb7 100644 --- a/actionview/lib/action_view/template/handlers/builder.rb +++ b/actionview/lib/action_view/template/handlers/builder.rb @@ -7,7 +7,7 @@ module ActionView        def call(template)          require_engine -        "xml = ::Builder::XmlMarkup.new(:indent => 2);" + +        "xml = ::Builder::XmlMarkup.new(:indent => 2);" \            "self.output_buffer = xml.target!;" +            template.source +            ";xml.target!;" diff --git a/actionview/lib/action_view/template/handlers/erb.rb b/actionview/lib/action_view/template/handlers/erb.rb index 5d047a6991..58c7fd1a88 100644 --- a/actionview/lib/action_view/template/handlers/erb.rb +++ b/actionview/lib/action_view/template/handlers/erb.rb @@ -1,79 +1,12 @@ -require "erubis" -  module ActionView    class Template      module Handlers -      class Erubis < ::Erubis::Eruby -        def add_preamble(src) -          @newline_pending = 0 -          src << "@output_buffer = output_buffer || ActionView::OutputBuffer.new;" -        end - -        def add_text(src, text) -          return if text.empty? - -          if text == "\n" -            @newline_pending += 1 -          else -            src << "@output_buffer.safe_append='" -            src << "\n" * @newline_pending if @newline_pending > 0 -            src << escape_text(text) -            src << "'.freeze;" - -            @newline_pending = 0 -          end -        end - -        # Erubis toggles <%= and <%== behavior when escaping is enabled. -        # We override to always treat <%== as escaped. -        def add_expr(src, code, indicator) -          case indicator -          when "==" -            add_expr_escaped(src, code) -          else -            super -          end -        end - -        BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/ - -        def add_expr_literal(src, code) -          flush_newline_if_pending(src) -          if BLOCK_EXPR.match?(code) -            src << "@output_buffer.append= " << code -          else -            src << "@output_buffer.append=(" << code << ");" -          end -        end - -        def add_expr_escaped(src, code) -          flush_newline_if_pending(src) -          if BLOCK_EXPR.match?(code) -            src << "@output_buffer.safe_expr_append= " << code -          else -            src << "@output_buffer.safe_expr_append=(" << code << ");" -          end -        end - -        def add_stmt(src, code) -          flush_newline_if_pending(src) -          super -        end - -        def add_postamble(src) -          flush_newline_if_pending(src) -          src << "@output_buffer.to_s" -        end - -        def flush_newline_if_pending(src) -          if @newline_pending > 0 -            src << "@output_buffer.safe_append='#{"\n" * @newline_pending}'.freeze;" -            @newline_pending = 0 -          end -        end -      end +      autoload :Erubis, "action_view/template/handlers/erb/deprecated_erubis"        class ERB +        autoload :Erubi, "action_view/template/handlers/erb/erubi" +        autoload :Erubis, "action_view/template/handlers/erb/erubis" +          # Specify trim mode for the ERB compiler. Defaults to '-'.          # See ERB documentation for suitable values.          class_attribute :erb_trim_mode @@ -81,7 +14,7 @@ module ActionView          # Default implementation used.          class_attribute :erb_implementation -        self.erb_implementation = Erubis +        self.erb_implementation = Erubi          # Do not escape templates of these mime types.          class_attribute :escape_whitelist diff --git a/actionview/lib/action_view/template/handlers/erb/deprecated_erubis.rb b/actionview/lib/action_view/template/handlers/erb/deprecated_erubis.rb new file mode 100644 index 0000000000..427ea20064 --- /dev/null +++ b/actionview/lib/action_view/template/handlers/erb/deprecated_erubis.rb @@ -0,0 +1,9 @@ +::ActiveSupport::Deprecation.warn("ActionView::Template::Handlers::Erubis is deprecated and will be removed from Rails 5.2. Switch to ActionView::Template::Handlers::ERB::Erubi instead.") + +module ActionView +  class Template +    module Handlers +      Erubis = ERB::Erubis +    end +  end +end diff --git a/actionview/lib/action_view/template/handlers/erb/erubi.rb b/actionview/lib/action_view/template/handlers/erb/erubi.rb new file mode 100644 index 0000000000..755cc84015 --- /dev/null +++ b/actionview/lib/action_view/template/handlers/erb/erubi.rb @@ -0,0 +1,81 @@ +require "erubi" + +module ActionView +  class Template +    module Handlers +      class ERB +        class Erubi < ::Erubi::Engine +          # :nodoc: all +          def initialize(input, properties = {}) +            @newline_pending = 0 + +            # Dup properties so that we don't modify argument +            properties = Hash[properties] +            properties[:preamble]   = "@output_buffer = output_buffer || ActionView::OutputBuffer.new;" +            properties[:postamble]  = "@output_buffer.to_s" +            properties[:bufvar]     = "@output_buffer" +            properties[:escapefunc] = "" + +            super +          end + +          def evaluate(action_view_erb_handler_context) +            pr = eval("proc { #{@src} }", binding, @filename || "(erubi)") +            action_view_erb_handler_context.instance_eval(&pr) +          end + +        private +          def add_text(text) +            return if text.empty? + +            if text == "\n" +              @newline_pending += 1 +            else +              src << "@output_buffer.safe_append='" +              src << "\n" * @newline_pending if @newline_pending > 0 +              src << text.gsub(/['\\]/, '\\\\\&') +              src << "'.freeze;" + +              @newline_pending = 0 +            end +          end + +          BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/ + +          def add_expression(indicator, code) +            flush_newline_if_pending(src) + +            if (indicator == "==") || @escape +              src << "@output_buffer.safe_expr_append=" +            else +              src << "@output_buffer.append=" +            end + +            if BLOCK_EXPR.match?(code) +              src << " " << code +            else +              src << "(" << code << ");" +            end +          end + +          def add_code(code) +            flush_newline_if_pending(src) +            super +          end + +          def add_postamble(_) +            flush_newline_if_pending(src) +            super +          end + +          def flush_newline_if_pending(src) +            if @newline_pending > 0 +              src << "@output_buffer.safe_append='#{"\n" * @newline_pending}'.freeze;" +              @newline_pending = 0 +            end +          end +        end +      end +    end +  end +end diff --git a/actionview/lib/action_view/template/handlers/erb/erubis.rb b/actionview/lib/action_view/template/handlers/erb/erubis.rb new file mode 100644 index 0000000000..f3c35e1aec --- /dev/null +++ b/actionview/lib/action_view/template/handlers/erb/erubis.rb @@ -0,0 +1,81 @@ +gem "erubis" +require "erubis" + +module ActionView +  class Template +    module Handlers +      class ERB +        class Erubis < ::Erubis::Eruby +          # :nodoc: all +          def add_preamble(src) +            @newline_pending = 0 +            src << "@output_buffer = output_buffer || ActionView::OutputBuffer.new;" +          end + +          def add_text(src, text) +            return if text.empty? + +            if text == "\n" +              @newline_pending += 1 +            else +              src << "@output_buffer.safe_append='" +              src << "\n" * @newline_pending if @newline_pending > 0 +              src << escape_text(text) +              src << "'.freeze;" + +              @newline_pending = 0 +            end +          end + +          # Erubis toggles <%= and <%== behavior when escaping is enabled. +          # We override to always treat <%== as escaped. +          def add_expr(src, code, indicator) +            case indicator +            when "==" +              add_expr_escaped(src, code) +            else +              super +            end +          end + +          BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/ + +          def add_expr_literal(src, code) +            flush_newline_if_pending(src) +            if BLOCK_EXPR.match?(code) +              src << "@output_buffer.append= " << code +            else +              src << "@output_buffer.append=(" << code << ");" +            end +          end + +          def add_expr_escaped(src, code) +            flush_newline_if_pending(src) +            if BLOCK_EXPR.match?(code) +              src << "@output_buffer.safe_expr_append= " << code +            else +              src << "@output_buffer.safe_expr_append=(" << code << ");" +            end +          end + +          def add_stmt(src, code) +            flush_newline_if_pending(src) +            super +          end + +          def add_postamble(src) +            flush_newline_if_pending(src) +            src << "@output_buffer.to_s" +          end + +          def flush_newline_if_pending(src) +            if @newline_pending > 0 +              src << "@output_buffer.safe_append='#{"\n" * @newline_pending}'.freeze;" +              @newline_pending = 0 +            end +          end +        end +      end +    end +  end +end diff --git a/actionview/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb index 9da13663d7..d3905b5f23 100644 --- a/actionview/lib/action_view/template/resolver.rb +++ b/actionview/lib/action_view/template/resolver.rb @@ -164,8 +164,8 @@ module ActionView      # This is what child classes implement. No defaults are needed      # because Resolver guarantees that the arguments are present and      # normalized. -    def find_templates(name, prefix, partial, details) -      raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details) method" +    def find_templates(name, prefix, partial, details, outside_app_allowed = false) +      raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details, outside_app_allowed = false) method"      end      # Helpers that builds a path. Useful for building virtual paths. @@ -226,7 +226,7 @@ module ActionView          template_paths = reject_files_external_to_app(template_paths) unless outside_app_allowed          template_paths.map do |template| -          handler, format, variant = extract_handler_and_format_and_variant(template, formats) +          handler, format, variant = extract_handler_and_format_and_variant(template)            contents = File.binread(template)            Template.new(contents, File.expand_path(template), handler, @@ -289,7 +289,7 @@ module ActionView        # Extract handler, formats and variant from path. If a format cannot be found neither        # from the path, or the handler, we should return the array of formats given        # to the resolver. -      def extract_handler_and_format_and_variant(path, default_formats) +      def extract_handler_and_format_and_variant(path)          pieces = File.basename(path).split(".".freeze)          pieces.shift diff --git a/actionview/lib/action_view/test_case.rb b/actionview/lib/action_view/test_case.rb index 5fb7bb54b5..2b981caa65 100644 --- a/actionview/lib/action_view/test_case.rb +++ b/actionview/lib/action_view/test_case.rb @@ -206,8 +206,8 @@ module ActionView            view = @controller.view_context            view.singleton_class.include(_helpers)            view.extend(Locals) -          view.rendered_views = self.rendered_views -          view.output_buffer = self.output_buffer +          view.rendered_views = rendered_views +          view.output_buffer = output_buffer            view          end        end diff --git a/actionview/lib/action_view/testing/resolvers.rb b/actionview/lib/action_view/testing/resolvers.rb index f4a7a9138c..3188526b63 100644 --- a/actionview/lib/action_view/testing/resolvers.rb +++ b/actionview/lib/action_view/testing/resolvers.rb @@ -17,35 +17,35 @@ module ActionView #:nodoc:        @hash.keys.join(", ")      end -  private - -    def query(path, exts, formats, _) -      query = "" -      EXTENSIONS.each_key do |ext| -        query << "(" << exts[ext].map { |e| e && Regexp.escape(".#{e}") }.join("|") << "|)" -      end -      query = /^(#{Regexp.escape(path)})#{query}$/ - -      templates = [] -      @hash.each do |_path, array| -        source, updated_at = array -        next unless query.match?(_path) -        handler, format, variant = extract_handler_and_format_and_variant(_path, formats) -        templates << Template.new(source, _path, handler, -          virtual_path: path.virtual, -          format: format, -          variant: variant, -          updated_at: updated_at -        ) +    private + +      def query(path, exts, _, _) +        query = "" +        EXTENSIONS.each_key do |ext| +          query << "(" << exts[ext].map { |e| e && Regexp.escape(".#{e}") }.join("|") << "|)" +        end +        query = /^(#{Regexp.escape(path)})#{query}$/ + +        templates = [] +        @hash.each do |_path, array| +          source, updated_at = array +          next unless query.match?(_path) +          handler, format, variant = extract_handler_and_format_and_variant(_path) +          templates << Template.new(source, _path, handler, +            virtual_path: path.virtual, +            format: format, +            variant: variant, +            updated_at: updated_at +          ) +        end + +        templates.sort_by { |t| -t.identifier.match(/^#{query}$/).captures.reject(&:blank?).size }        end - -      templates.sort_by { |t| -t.identifier.match(/^#{query}$/).captures.reject(&:blank?).size } -    end    end    class NullResolver < PathResolver -    def query(path, exts, formats, _) -      handler, format, variant = extract_handler_and_format_and_variant(path, formats) +    def query(path, exts, _, _) +      handler, format, variant = extract_handler_and_format_and_variant(path)        [ActionView::Template.new("Template generated by Null Resolver", path.virtual, handler, virtual_path: path.virtual, format: format, variant: variant)]      end    end diff --git a/actionview/lib/action_view/view_paths.rb b/actionview/lib/action_view/view_paths.rb index a9638d1e6d..f0fe6831fa 100644 --- a/actionview/lib/action_view/view_paths.rb +++ b/actionview/lib/action_view/view_paths.rb @@ -5,7 +5,7 @@ module ActionView      included do        class_attribute :_view_paths        self._view_paths = ActionView::PathSet.new -      self._view_paths.freeze +      _view_paths.freeze      end      delegate :template_exists?, :any_templates?, :view_paths, :formats, :formats=, diff --git a/actionview/test/abstract_unit.rb b/actionview/test/abstract_unit.rb index 5bc4151087..dde66a7ba0 100644 --- a/actionview/test/abstract_unit.rb +++ b/actionview/test/abstract_unit.rb @@ -9,8 +9,8 @@ require "active_support/core_ext/kernel/reporting"  # These are the normal settings that will be set up by Railties  # TODO: Have these tests support other combinations of these values  silence_warnings do -  Encoding.default_internal = "UTF-8" -  Encoding.default_external = "UTF-8" +  Encoding.default_internal = Encoding::UTF_8 +  Encoding.default_external = Encoding::UTF_8  end  require "active_support/testing/autorun" @@ -271,15 +271,15 @@ module ActionDispatch    end  end -# Skips the current run on Rubinius using Minitest::Assertions#skip -def rubinius_skip(message = "") -  skip message if RUBY_ENGINE == "rbx" -end -# Skips the current run on JRuby using Minitest::Assertions#skip -def jruby_skip(message = "") -  skip message if defined?(JRUBY_VERSION) -end -  class ActiveSupport::TestCase    include ActiveSupport::Testing::MethodCallAssertions + +  # Skips the current run on Rubinius using Minitest::Assertions#skip +  private def rubinius_skip(message = "") +    skip message if RUBY_ENGINE == "rbx" +  end +  # Skips the current run on JRuby using Minitest::Assertions#skip +  private def jruby_skip(message = "") +    skip message if defined?(JRUBY_VERSION) +  end  end diff --git a/actionview/test/actionpack/controller/render_test.rb b/actionview/test/actionpack/controller/render_test.rb index b4a757d6b8..51ec8899b1 100644 --- a/actionview/test/actionpack/controller/render_test.rb +++ b/actionview/test/actionpack/controller/render_test.rb @@ -1,45 +1,14 @@  require "abstract_unit"  require "active_model" +require "controller/fake_models"  class ApplicationController < ActionController::Base    self.view_paths = File.join(FIXTURE_LOAD_PATH, "actionpack")  end -class Customer < Struct.new(:name, :id) -  extend ActiveModel::Naming -  include ActiveModel::Conversion - -  undef_method :to_json - -  def to_xml(options = {}) -    if options[:builder] -      options[:builder].name name -    else -      "<name>#{name}</name>" -    end -  end - -  def to_js(options = {}) -    "name: #{name.inspect}" -  end -  alias :to_text :to_js - -  def errors -    [] -  end - -  def persisted? -    id.present? -  end - -  def cache_key -    name.to_s -  end -end -  module Quiz    #Models -  class Question < Struct.new(:name, :id) +  Question = Struct.new(:name, :id) do      extend ActiveModel::Naming      include ActiveModel::Conversion @@ -56,9 +25,6 @@ module Quiz    end  end -class BadCustomer < Customer; end -class GoodCustomer < Customer; end -  module Fun    class GamesController < ApplicationController      def hello_world; end diff --git a/actionview/test/activerecord/form_helper_activerecord_test.rb b/actionview/test/activerecord/form_helper_activerecord_test.rb index 0f7960b408..3b314588c7 100644 --- a/actionview/test/activerecord/form_helper_activerecord_test.rb +++ b/actionview/test/activerecord/form_helper_activerecord_test.rb @@ -45,7 +45,7 @@ class FormHelperActiveRecordTest < ActionView::TestCase      end      expected = whole_form("/developers/123", "edit_developer_123", "edit_developer", method: "patch") do -      '<input id="developer_projects_attributes_abc_name" name="developer[projects_attributes][abc][name]" type="text" value="project #321" />' + +      '<input id="developer_projects_attributes_abc_name" name="developer[projects_attributes][abc][name]" type="text" value="project #321" />' \            '<input id="developer_projects_attributes_abc_id" name="developer[projects_attributes][abc][id]" type="hidden" value="321" />'      end diff --git a/actionview/test/activerecord/polymorphic_routes_test.rb b/actionview/test/activerecord/polymorphic_routes_test.rb index 8495949975..dcad0b424c 100644 --- a/actionview/test/activerecord/polymorphic_routes_test.rb +++ b/actionview/test/activerecord/polymorphic_routes_test.rb @@ -45,7 +45,7 @@ class ModelDelegate    end  end -module Blog +module Weblog    class Post < ActiveRecord::Base      self.table_name = "projects"    end @@ -61,7 +61,7 @@ end  class PolymorphicRoutesTest < ActionController::TestCase    include SharedTestRoutes.url_helpers -  self.default_url_options[:host] = "example.com" +  default_url_options[:host] = "example.com"    def setup      @project = Project.new @@ -72,8 +72,8 @@ class PolymorphicRoutesTest < ActionController::TestCase      @fax = Fax.new      @delegator = ModelDelegator.new      @series = Series.new -    @blog_post = Blog::Post.new -    @blog_blog = Blog::Blog.new +    @blog_post = Weblog::Post.new +    @blog_blog = Weblog::Blog.new    end    def assert_url(url, args) diff --git a/actionview/test/activerecord/render_partial_with_record_identification_test.rb b/actionview/test/activerecord/render_partial_with_record_identification_test.rb index 55886da30f..60c3ab3045 100644 --- a/actionview/test/activerecord/render_partial_with_record_identification_test.rb +++ b/actionview/test/activerecord/render_partial_with_record_identification_test.rb @@ -87,7 +87,7 @@ class RenderPartialWithRecordIdentificationTest < ActiveRecordTestCase    end  end -class Game < Struct.new(:name, :id) +Game = Struct.new(:name, :id) do    extend ActiveModel::Naming    include ActiveModel::Conversion    def to_param diff --git a/actionview/test/fixtures/test/_partial_iteration_1.erb b/actionview/test/fixtures/test/_partial_iteration_1.erb new file mode 100644 index 0000000000..c0fdd4c22a --- /dev/null +++ b/actionview/test/fixtures/test/_partial_iteration_1.erb @@ -0,0 +1 @@ +<%= defined?(partial_iteration_1_iteration) %> diff --git a/actionview/test/fixtures/test/_partial_iteration_2.erb b/actionview/test/fixtures/test/_partial_iteration_2.erb new file mode 100644 index 0000000000..50dd11db27 --- /dev/null +++ b/actionview/test/fixtures/test/_partial_iteration_2.erb @@ -0,0 +1 @@ +<%= defined?(partial_iteration_2_iteration) -%> diff --git a/actionview/test/fixtures/test/render_file_instance_variable.erb b/actionview/test/fixtures/test/render_file_instance_variable.erb new file mode 100644 index 0000000000..5344ac8a66 --- /dev/null +++ b/actionview/test/fixtures/test/render_file_instance_variable.erb @@ -0,0 +1 @@ +<%= @foo %>
\ No newline at end of file diff --git a/actionview/test/lib/controller/fake_models.rb b/actionview/test/lib/controller/fake_models.rb index 80649db88b..5250101220 100644 --- a/actionview/test/lib/controller/fake_models.rb +++ b/actionview/test/lib/controller/fake_models.rb @@ -1,6 +1,6 @@  require "active_model" -class Customer < Struct.new(:name, :id) +Customer = Struct.new(:name, :id) do    extend ActiveModel::Naming    include ActiveModel::Conversion @@ -26,12 +26,16 @@ class Customer < Struct.new(:name, :id)    def persisted?      id.present?    end -end -class GoodCustomer < Customer +  def cache_key +    name.to_s +  end  end -class Post < Struct.new(:title, :author_name, :body, :secret, :persisted, :written_on, :cost) +class BadCustomer < Customer; end +class GoodCustomer < Customer; end + +Post = Struct.new(:title, :author_name, :body, :secret, :persisted, :written_on, :cost) do    extend ActiveModel::Naming    include ActiveModel::Conversion    extend ActiveModel::Translation @@ -80,7 +84,7 @@ class Comment    def to_key; id ? [id] : nil end    def save; @id = 1; @post_id = 1 end    def persisted?; @id.present? end -  def to_param; @id.to_s; end +  def to_param; @id && @id.to_s; end    def name      @id.nil? ? "new #{self.class.name.downcase}" : "#{self.class.name.downcase} ##{@id}"    end @@ -101,7 +105,7 @@ class Tag    def to_key; id ? [id] : nil end    def save; @id = 1; @post_id = 1 end    def persisted?; @id.present? end -  def to_param; @id; end +  def to_param; @id && @id.to_s; end    def value      @id.nil? ? "new #{self.class.name.downcase}" : "#{self.class.name.downcase} ##{@id}"    end @@ -120,7 +124,7 @@ class CommentRelevance    def to_key; id ? [id] : nil end    def save; @id = 1; @comment_id = 1 end    def persisted?; @id.present? end -  def to_param; @id; end +  def to_param; @id && @id.to_s; end    def value      @id.nil? ? "new #{self.class.name.downcase}" : "#{self.class.name.downcase} ##{@id}"    end @@ -136,7 +140,7 @@ class TagRelevance    def to_key; id ? [id] : nil end    def save; @id = 1; @tag_id = 1 end    def persisted?; @id.present? end -  def to_param; @id; end +  def to_param; @id && @id.to_s; end    def value      @id.nil? ? "new #{self.class.name.downcase}" : "#{self.class.name.downcase} ##{@id}"    end @@ -163,7 +167,7 @@ module Blog      true    end -  class Post < Struct.new(:title, :id) +  Post = Struct.new(:title, :id) do      extend ActiveModel::Naming      include ActiveModel::Conversion @@ -183,8 +187,7 @@ class ArelLike    end  end -class Car < Struct.new(:color) -end +Car = Struct.new(:color)  class Plane    attr_reader :to_key diff --git a/actionview/test/template/active_model_helper_test.rb b/actionview/test/template/active_model_helper_test.rb index 8b8f686f96..6b63aa25a5 100644 --- a/actionview/test/template/active_model_helper_test.rb +++ b/actionview/test/template/active_model_helper_test.rb @@ -4,7 +4,7 @@ class ActiveModelHelperTest < ActionView::TestCase    tests ActionView::Helpers::ActiveModelHelper    silence_warnings do -    class Post < Struct.new(:author_name, :body, :updated_at) +    Post = Struct.new(:author_name, :body, :updated_at) do        include ActiveModel::Conversion        include ActiveModel::Validations diff --git a/actionview/test/template/atom_feed_helper_test.rb b/actionview/test/template/atom_feed_helper_test.rb index e9a923dd72..1245a1a966 100644 --- a/actionview/test/template/atom_feed_helper_test.rb +++ b/actionview/test/template/atom_feed_helper_test.rb @@ -1,6 +1,6 @@  require "abstract_unit" -class Scroll < Struct.new(:id, :to_param, :title, :body, :updated_at, :created_at) +Scroll = Struct.new(:id, :to_param, :title, :body, :updated_at, :created_at) do    extend ActiveModel::Naming    include ActiveModel::Conversion diff --git a/actionview/test/template/compiled_templates_test.rb b/actionview/test/template/compiled_templates_test.rb index 40ac867b38..adb2be9be4 100644 --- a/actionview/test/template/compiled_templates_test.rb +++ b/actionview/test/template/compiled_templates_test.rb @@ -38,6 +38,10 @@ class CompiledTemplatesTest < ActiveSupport::TestCase      assert_equal "🎂", render(file: "test/render_file_unicode_local", locals: { 🎃: "🎂" })    end +  def test_template_with_instance_variable_identifier +    assert_equal "bar", render(file: "test/render_file_instance_variable", locals: { "@foo": "bar" }) +  end +    def test_template_gets_recompiled_when_using_different_keys_in_local_assigns      assert_equal "one", render(file: "test/render_file_with_locals_and_default")      assert_equal "two", render(file: "test/render_file_with_locals_and_default", locals: { secret: "two" }) diff --git a/actionview/test/template/erb/deprecated_erubis_implementation_test.rb b/actionview/test/template/erb/deprecated_erubis_implementation_test.rb new file mode 100644 index 0000000000..aaf99f85c0 --- /dev/null +++ b/actionview/test/template/erb/deprecated_erubis_implementation_test.rb @@ -0,0 +1,13 @@ +require "abstract_unit" + +module ERBTest +  class DeprecatedErubisImplementationTest < ActionView::TestCase +    test "Erubis implementation is deprecated" do +      assert_deprecated "ActionView::Template::Handlers::Erubis is deprecated and will be removed from Rails 5.2. Switch to ActionView::Template::Handlers::ERB::Erubi instead." do +        assert_equal "ActionView::Template::Handlers::ERB::Erubis", ActionView::Template::Handlers::Erubis.to_s + +        assert_nothing_raised { Class.new(ActionView::Template::Handlers::Erubis) } +      end +    end +  end +end diff --git a/actionview/test/template/erb/helper.rb b/actionview/test/template/erb/helper.rb index a1973068d5..bc1eedc15f 100644 --- a/actionview/test/template/erb/helper.rb +++ b/actionview/test/template/erb/helper.rb @@ -14,7 +14,7 @@ module ERBTest    class BlockTestCase < ActiveSupport::TestCase      def render_content(start, inside)        template = block_helper(start, inside) -      ActionView::Template::Handlers::Erubis.new(template).evaluate(ViewContext.new) +      ActionView::Template::Handlers::ERB.erb_implementation.new(template).evaluate(ViewContext.new)      end      def block_helper(str, rest) diff --git a/actionview/test/template/erb_util_test.rb b/actionview/test/template/erb_util_test.rb index 0a16364ed3..4412ea37fa 100644 --- a/actionview/test/template/erb_util_test.rb +++ b/actionview/test/template/erb_util_test.rb @@ -51,7 +51,12 @@ class ErbUtilTest < ActiveSupport::TestCase    def test_json_escape_does_not_alter_json_string_meaning      JSON_ESCAPE_TEST_CASES.each do |(raw, _)| -      assert_equal ActiveSupport::JSON.decode(raw), ActiveSupport::JSON.decode(json_escape(raw)) +      expected = ActiveSupport::JSON.decode(raw) +      if expected.nil? +        assert_nil ActiveSupport::JSON.decode(json_escape(raw)) +      else +        assert_equal expected, ActiveSupport::JSON.decode(json_escape(raw)) +      end      end    end diff --git a/actionview/test/template/form_collections_helper_test.rb b/actionview/test/template/form_collections_helper_test.rb index 3774dcf872..6160524eb3 100644 --- a/actionview/test/template/form_collections_helper_test.rb +++ b/actionview/test/template/form_collections_helper_test.rb @@ -1,7 +1,6 @@  require "abstract_unit" -class Category < Struct.new(:id, :name) -end +Category = Struct.new(:id, :name)  class FormCollectionsHelperTest < ActionView::TestCase    def assert_no_select(selector, value = nil) diff --git a/actionview/test/template/form_helper/form_with_test.rb b/actionview/test/template/form_helper/form_with_test.rb index 08c685b00f..a4a2966ff9 100644 --- a/actionview/test/template/form_helper/form_with_test.rb +++ b/actionview/test/template/form_helper/form_with_test.rb @@ -310,13 +310,13 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/posts/123", "create-post", method: "patch") do -      "<label for='post_title'>The Title</label>" + -      "<input name='post[title]' type='text' value='Hello World' />" + -      "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" + -      "<input name='post[secret]' type='hidden' value='0' />" + -      "<input name='post[secret]' checked='checked' type='checkbox' value='1' />" + -      "<input name='commit' data-disable-with='Create post' type='submit' value='Create post' />" + -      "<button name='button' type='submit'>Create post</button>" + +      "<label for='post_title'>The Title</label>" \ +      "<input name='post[title]' type='text' value='Hello World' />" \ +      "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" \ +      "<input name='post[secret]' type='hidden' value='0' />" \ +      "<input name='post[secret]' checked='checked' type='checkbox' value='1' />" \ +      "<input name='commit' data-disable-with='Create post' type='submit' value='Create post' />" \ +      "<button name='button' type='submit'>Create post</button>" \        "<button name='button' type='submit'><span>Create post</span></button>"      end @@ -330,7 +330,7 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/posts") do -      '<label for="title">Label me</label>' + +      '<label for="title">Label me</label>' \        '<input type="text" name="title">'      end @@ -339,12 +339,12 @@ class FormWithActsLikeFormForTest < FormWithTest    def test_form_with_only_url_on_update      form_with(url: "/posts/123") do |f| -      concat f.label :title, 'Label me' +      concat f.label :title, "Label me"        concat f.text_field :title      end      expected = whole_form("/posts/123") do -      '<label for="title">Label me</label>' + +      '<label for="title">Label me</label>' \        '<input type="text" name="title">'      end @@ -400,10 +400,10 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/posts") do -      "<input type='hidden' name='post[active]' value='' />" + -      "<input name='post[active]' type='radio' value='true' />" + -      "<label for='post_active_true'>true</label>" + -      "<input checked='checked' name='post[active]' type='radio' value='false' />" + +      "<input type='hidden' name='post[active]' value='' />" \ +      "<input name='post[active]' type='radio' value='true' />" \ +      "<label for='post_active_true'>true</label>" \ +      "<input checked='checked' name='post[active]' type='radio' value='false' />" \        "<label for='post_active_false'>false</label>"      end @@ -422,12 +422,12 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/posts") do -      "<input type='hidden' name='post[active]' value='' />" + -      "<label for='post_active_true'>" + -      "<input name='post[active]' type='radio' value='true' />" + -      "true</label>" + -      "<label for='post_active_false'>" + -      "<input checked='checked' name='post[active]' type='radio' value='false' />" + +      "<input type='hidden' name='post[active]' value='' />" \ +      "<label for='post_active_true'>" \ +      "<input name='post[active]' type='radio' value='true' />" \ +      "true</label>" \ +      "<label for='post_active_false'>" \ +      "<input checked='checked' name='post[active]' type='radio' value='false' />" \        "false</label>"      end @@ -448,13 +448,13 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/posts") do -      "<input type='hidden' name='post[active]' value='' />" + -      "<label for='post_active_true'>" + -      "<input name='post[active]' type='radio' value='true' />" + -      "true</label>" + -      "<label for='post_active_false'>" + -      "<input checked='checked' name='post[active]' type='radio' value='false' />" + -      "false</label>" + +      "<input type='hidden' name='post[active]' value='' />" \ +      "<label for='post_active_true'>" \ +      "<input name='post[active]' type='radio' value='true' />" \ +      "true</label>" \ +      "<label for='post_active_false'>" \ +      "<input checked='checked' name='post[active]' type='radio' value='false' />" \ +      "false</label>" \        "<input name='post[id]' type='hidden' value='1' />"      end @@ -470,10 +470,10 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/posts") do -      "<input type='hidden' name='post[1][active]' value='' />" + -      "<input name='post[1][active]' type='radio' value='true' />" + -      "<label for='post_1_active_true'>true</label>" + -      "<input checked='checked' name='post[1][active]' type='radio' value='false' />" + +      "<input type='hidden' name='post[1][active]' value='' />" \ +      "<input name='post[1][active]' type='radio' value='true' />" \ +      "<label for='post_1_active_true'>true</label>" \ +      "<input checked='checked' name='post[1][active]' type='radio' value='false' />" \        "<label for='post_1_active_false'>false</label>"      end @@ -489,12 +489,12 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/posts") do -      "<input name='post[tag_ids][]' type='hidden' value='' />" + -      "<input checked='checked' name='post[tag_ids][]' type='checkbox' value='1' />" + -      "<label for='post_tag_ids_1'>Tag 1</label>" + -      "<input name='post[tag_ids][]' type='checkbox' value='2' />" + -      "<label for='post_tag_ids_2'>Tag 2</label>" + -      "<input checked='checked' name='post[tag_ids][]' type='checkbox' value='3' />" + +      "<input name='post[tag_ids][]' type='hidden' value='' />" \ +      "<input checked='checked' name='post[tag_ids][]' type='checkbox' value='1' />" \ +      "<label for='post_tag_ids_1'>Tag 1</label>" \ +      "<input name='post[tag_ids][]' type='checkbox' value='2' />" \ +      "<label for='post_tag_ids_2'>Tag 2</label>" \ +      "<input checked='checked' name='post[tag_ids][]' type='checkbox' value='3' />" \        "<label for='post_tag_ids_3'>Tag 3</label>"      end @@ -513,15 +513,15 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/posts") do -      "<input name='post[tag_ids][]' type='hidden' value='' />" + -      "<label for='post_tag_ids_1'>" + -      "<input checked='checked' name='post[tag_ids][]' type='checkbox' value='1' />" + -      "Tag 1</label>" + -      "<label for='post_tag_ids_2'>" + -      "<input name='post[tag_ids][]' type='checkbox' value='2' />" + -      "Tag 2</label>" + -      "<label for='post_tag_ids_3'>" + -      "<input checked='checked' name='post[tag_ids][]' type='checkbox' value='3' />" + +      "<input name='post[tag_ids][]' type='hidden' value='' />" \ +      "<label for='post_tag_ids_1'>" \ +      "<input checked='checked' name='post[tag_ids][]' type='checkbox' value='1' />" \ +      "Tag 1</label>" \ +      "<label for='post_tag_ids_2'>" \ +      "<input name='post[tag_ids][]' type='checkbox' value='2' />" \ +      "Tag 2</label>" \ +      "<label for='post_tag_ids_3'>" \ +      "<input checked='checked' name='post[tag_ids][]' type='checkbox' value='3' />" \        "Tag 3</label>"      end @@ -543,16 +543,16 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/posts") do -      "<input name='post[tag_ids][]' type='hidden' value='' />" + -      "<label for='post_tag_ids_1'>" + -      "<input checked='checked' name='post[tag_ids][]' type='checkbox' value='1' />" + -      "Tag 1</label>" + -      "<label for='post_tag_ids_2'>" + -      "<input name='post[tag_ids][]' type='checkbox' value='2' />" + -      "Tag 2</label>" + -      "<label for='post_tag_ids_3'>" + -      "<input checked='checked' name='post[tag_ids][]' type='checkbox' value='3' />" + -      "Tag 3</label>" + +      "<input name='post[tag_ids][]' type='hidden' value='' />" \ +      "<label for='post_tag_ids_1'>" \ +      "<input checked='checked' name='post[tag_ids][]' type='checkbox' value='1' />" \ +      "Tag 1</label>" \ +      "<label for='post_tag_ids_2'>" \ +      "<input name='post[tag_ids][]' type='checkbox' value='2' />" \ +      "Tag 2</label>" \ +      "<label for='post_tag_ids_3'>" \ +      "<input checked='checked' name='post[tag_ids][]' type='checkbox' value='3' />" \ +      "Tag 3</label>" \        "<input name='post[id]' type='hidden' value='1' />"      end @@ -569,8 +569,8 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/posts") do -      "<input name='post[1][tag_ids][]' type='hidden' value='' />" + -      "<input checked='checked' name='post[1][tag_ids][]' type='checkbox' value='1' />" + +      "<input name='post[1][tag_ids][]' type='hidden' value='' />" \ +      "<input checked='checked' name='post[1][tag_ids][]' type='checkbox' value='1' />" \        "<label for='post_1_tag_ids_1'>Tag 1</label>"      end @@ -636,7 +636,7 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/posts/44", method: "patch") do -      "<input name='post[title]' type='text' value='And his name will be forty and four.' />" + +      "<input name='post[title]' type='text' value='And his name will be forty and four.' />" \        "<input name='commit' data-disable-with='Edit post' type='submit' value='Edit post' />"      end @@ -653,11 +653,11 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/posts/123", "create-post", method: "patch") do -      "<label for='other_name_title' class='post_title'>Title</label>" + -      "<input name='other_name[title]' value='Hello World' type='text' />" + -      "<textarea name='other_name[body]'>\nBack to the hill and over it again!</textarea>" + -      "<input name='other_name[secret]' value='0' type='hidden' />" + -      "<input name='other_name[secret]' checked='checked' value='1' type='checkbox' />" + +      "<label for='other_name_title' class='post_title'>Title</label>" \ +      "<input name='other_name[title]' value='Hello World' type='text' />" \ +      "<textarea name='other_name[body]'>\nBack to the hill and over it again!</textarea>" \ +      "<input name='other_name[secret]' value='0' type='hidden' />" \ +      "<input name='other_name[secret]' checked='checked' value='1' type='checkbox' />" \        "<input name='commit' value='Create post' data-disable-with='Create post' type='submit' />"      end @@ -672,9 +672,9 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/", "create-post", method: "delete") do -      "<input name='post[title]' type='text' value='Hello World' />" + -      "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" + -      "<input name='post[secret]' type='hidden' value='0' />" + +      "<input name='post[title]' type='text' value='Hello World' />" \ +      "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" \ +      "<input name='post[secret]' type='hidden' value='0' />" \        "<input name='post[secret]' checked='checked' type='checkbox' value='1' />"      end @@ -689,9 +689,9 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/", "create-post", method: "delete") do -      "<input name='post[title]' type='text' value='Hello World' />" + -      "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" + -      "<input name='post[secret]' type='hidden' value='0' />" + +      "<input name='post[title]' type='text' value='Hello World' />" \ +      "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" \ +      "<input name='post[secret]' type='hidden' value='0' />" \        "<input name='post[secret]' checked='checked' type='checkbox' value='1' />"      end @@ -720,9 +720,9 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/", "create-post", method: "patch") do -      "<input name='post[title]' type='text' value='Hello World' />" + -      "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" + -      "<input name='post[secret]' type='hidden' value='0' />" + +      "<input name='post[title]' type='text' value='Hello World' />" \ +      "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" \ +      "<input name='post[secret]' type='hidden' value='0' />" \        "<input name='post[secret]' checked='checked' type='checkbox' value='1' />"      end @@ -761,9 +761,9 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/", "create-post") do -      "<input name='post[title]' type='text' value='Hello World' />" + -      "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" + -      "<input name='post[secret]' type='hidden' value='0' />" + +      "<input name='post[title]' type='text' value='Hello World' />" \ +      "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" \ +      "<input name='post[secret]' type='hidden' value='0' />" \        "<input name='post[secret]' checked='checked' type='checkbox' value='1' />"      end @@ -779,10 +779,10 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/posts/123", method: "patch") do -      "<label for='post_123_title'>Title</label>" + -      "<input name='post[123][title]' type='text' value='Hello World' />" + -      "<textarea name='post[123][body]'>\nBack to the hill and over it again!</textarea>" + -      "<input name='post[123][secret]' type='hidden' value='0' />" + +      "<label for='post_123_title'>Title</label>" \ +      "<input name='post[123][title]' type='text' value='Hello World' />" \ +      "<textarea name='post[123][body]'>\nBack to the hill and over it again!</textarea>" \ +      "<input name='post[123][secret]' type='hidden' value='0' />" \        "<input name='post[123][secret]' checked='checked' type='checkbox' value='1' />"      end @@ -797,9 +797,9 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/posts/123", method: "patch") do -      "<input name='post[][title]' type='text' value='Hello World' />" + -      "<textarea name='post[][body]'>\nBack to the hill and over it again!</textarea>" + -      "<input name='post[][secret]' type='hidden' value='0' />" + +      "<input name='post[][title]' type='text' value='Hello World' />" \ +      "<textarea name='post[][body]'>\nBack to the hill and over it again!</textarea>" \ +      "<input name='post[][secret]' type='hidden' value='0' />" \        "<input name='post[][secret]' checked='checked' type='checkbox' value='1' />"      end @@ -814,8 +814,8 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/posts/123", method: "patch") do -      "<div class='field_with_errors'><label for='post_author_name' class='label'>Author name</label></div>" + -      "<div class='field_with_errors'><input name='post[author_name]' type='text' value='' /></div>" + +      "<div class='field_with_errors'><label for='post_author_name' class='label'>Author name</label></div>" \ +      "<div class='field_with_errors'><input name='post[author_name]' type='text' value='' /></div>" \        "<input name='commit' data-disable-with='Create post' type='submit' value='Create post' />"      end @@ -832,8 +832,8 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/posts/123", method: "patch") do -      "<div class='field_with_errors'><label for='post_author_name' class='label'>Author name</label></div>" + -      "<div class='field_with_errors'><input name='post[author_name]' type='text' value='' /></div>" + +      "<div class='field_with_errors'><label for='post_author_name' class='label'>Author name</label></div>" \ +      "<div class='field_with_errors'><input name='post[author_name]' type='text' value='' /></div>" \        "<input name='commit' data-disable-with='Create post' type='submit' value='Create post' />"      end @@ -847,7 +847,7 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/posts/123", method: "patch") do -      "<div class='field_with_errors'><label for='post_author_name' class='label'>Name</label></div>" + +      "<div class='field_with_errors'><label for='post_author_name' class='label'>Name</label></div>" \        "<div class='field_with_errors'><label for='post_author_name' class='label'>Name</label></div>"      end @@ -863,9 +863,9 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/posts/123", "namespace_edit_post_123", "edit_post", method: "patch") do -      "<input name='post[title]' type='text' value='Hello World' />" + -      "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" + -      "<input name='post[secret]' type='hidden' value='0' />" + +      "<input name='post[title]' type='text' value='Hello World' />" \ +      "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" \ +      "<input name='post[secret]' type='hidden' value='0' />" \        "<input name='post[secret]' checked='checked' type='checkbox' value='1' />"      end @@ -965,7 +965,6 @@ class FormWithActsLikeFormForTest < FormWithTest      assert_dom_equal expected, output_buffer    end -    def test_nested_fields      @comment.body = "Hello World"      form_with(model: @post) do |f| @@ -1010,7 +1009,7 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/posts/123", method: "patch") do -      "<input name='post[123][title]' type='text' value='Hello World' />" + +      "<input name='post[123][title]' type='text' value='Hello World' />" \        "<input name='post[123][comment][][name]' type='text' value='new comment' />"      end @@ -1026,7 +1025,7 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/posts/123", method: "patch") do -      "<input name='post[1][title]' type='text' value='Hello World' />" + +      "<input name='post[1][title]' type='text' value='Hello World' />" \        "<input name='post[1][comment][1][name]' type='text' value='new comment' />"      end @@ -1136,7 +1135,7 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/posts/123", method: "patch") do -      '<input name="post[title]" type="text" value="Hello World" />' + +      '<input name="post[title]" type="text" value="Hello World" />' \          '<input name="post[author_attributes][name]" type="text" value="new author" />'      end @@ -1163,8 +1162,8 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/posts/123", method: "patch") do -      '<input name="post[title]" type="text" value="Hello World" />' + -      '<input name="post[author_attributes][name]" type="text" value="author #321" />' + +      '<input name="post[title]" type="text" value="Hello World" />' \ +      '<input name="post[author_attributes][name]" type="text" value="author #321" />' \        '<input name="post[author_attributes][id]" type="hidden" value="321" />'      end @@ -1182,8 +1181,8 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/posts/123", method: "patch") do -      '<input name="post[title]" type="text" value="Hello World" />' + -      '<input name="post[author_attributes][name]" type="text" value="author #321" />' + +      '<input name="post[title]" type="text" value="Hello World" />' \ +      '<input name="post[author_attributes][name]" type="text" value="author #321" />' \        '<input name="post[author_attributes][id]" type="hidden" value="321" />'      end @@ -1201,7 +1200,7 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/posts/123", method: "patch") do -      '<input name="post[title]" type="text" value="Hello World" />' + +      '<input name="post[title]" type="text" value="Hello World" />' \        '<input name="post[author_attributes][name]" type="text" value="author #321" />'      end @@ -1219,7 +1218,7 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/posts/123", method: "patch") do -      '<input name="post[title]" type="text" value="Hello World" />' + +      '<input name="post[title]" type="text" value="Hello World" />' \        '<input name="post[author_attributes][name]" type="text" value="author #321" />'      end @@ -1237,8 +1236,8 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/posts/123", method: "patch") do -      '<input name="post[title]" type="text" value="Hello World" />' + -      '<input name="post[author_attributes][name]" type="text" value="author #321" />' + +      '<input name="post[title]" type="text" value="Hello World" />' \ +      '<input name="post[author_attributes][name]" type="text" value="author #321" />' \        '<input name="post[author_attributes][id]" type="hidden" value="321" />'      end @@ -1257,8 +1256,8 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/posts/123", method: "patch") do -      '<input name="post[title]" type="text" value="Hello World" />' + -      '<input name="post[author_attributes][id]" type="hidden" value="321" />' + +      '<input name="post[title]" type="text" value="Hello World" />' \ +      '<input name="post[author_attributes][id]" type="hidden" value="321" />' \        '<input name="post[author_attributes][name]" type="text" value="author #321" />'      end @@ -1278,10 +1277,10 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/posts/123", method: "patch") do -      '<input name="post[title]" type="text" value="Hello World" />' + -      '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + -      '<input name="post[comments_attributes][0][id]" type="hidden" value="1" />' + -      '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" />' + +      '<input name="post[title]" type="text" value="Hello World" />' \ +      '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" />' \ +      '<input name="post[comments_attributes][0][id]" type="hidden" value="1" />' \ +      '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" />' \        '<input name="post[comments_attributes][1][id]" type="hidden" value="2" />'      end @@ -1305,10 +1304,10 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/posts/123", method: "patch") do -      '<input name="post[title]" type="text" value="Hello World" />' + -      '<input name="post[author_attributes][name]" type="text" value="author #321" />' + -      '<input name="post[author_attributes][id]" type="hidden" value="321" />' + -      '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + +      '<input name="post[title]" type="text" value="Hello World" />' \ +      '<input name="post[author_attributes][name]" type="text" value="author #321" />' \ +      '<input name="post[author_attributes][id]" type="hidden" value="321" />' \ +      '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" />' \        '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" />'      end @@ -1332,9 +1331,9 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/posts/123", method: "patch") do -      '<input name="post[title]" type="text" value="Hello World" />' + -      '<input name="post[author_attributes][name]" type="text" value="author #321" />' + -      '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + +      '<input name="post[title]" type="text" value="Hello World" />' \ +      '<input name="post[author_attributes][name]" type="text" value="author #321" />' \ +      '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" />' \        '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" />'      end @@ -1358,10 +1357,10 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/posts/123", method: "patch") do -      '<input name="post[title]" type="text" value="Hello World" />' + -      '<input name="post[author_attributes][name]" type="text" value="author #321" />' + -      '<input name="post[author_attributes][id]" type="hidden" value="321" />' + -      '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + +      '<input name="post[title]" type="text" value="Hello World" />' \ +      '<input name="post[author_attributes][name]" type="text" value="author #321" />' \ +      '<input name="post[author_attributes][id]" type="hidden" value="321" />' \ +      '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" />' \        '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" />'      end @@ -1381,10 +1380,10 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/posts/123", method: "patch") do -      '<input name="post[title]" type="text" value="Hello World" />' + -      '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + -      '<input name="post[comments_attributes][0][id]" type="hidden" value="1" />' + -      '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" />' + +      '<input name="post[title]" type="text" value="Hello World" />' \ +      '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" />' \ +      '<input name="post[comments_attributes][0][id]" type="hidden" value="1" />' \ +      '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" />' \        '<input name="post[comments_attributes][1][id]" type="hidden" value="2" />'      end @@ -1405,10 +1404,10 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/posts/123", method: "patch") do -      '<input name="post[title]" type="text" value="Hello World" />' + -      '<input name="post[comments_attributes][0][id]" type="hidden" value="1" />' + -      '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + -      '<input name="post[comments_attributes][1][id]" type="hidden" value="2" />' + +      '<input name="post[title]" type="text" value="Hello World" />' \ +      '<input name="post[comments_attributes][0][id]" type="hidden" value="1" />' \ +      '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" />' \ +      '<input name="post[comments_attributes][1][id]" type="hidden" value="2" />' \        '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" />'      end @@ -1428,8 +1427,8 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/posts/123", method: "patch") do -      '<input name="post[title]" type="text" value="Hello World" />' + -      '<input name="post[comments_attributes][0][name]" type="text" value="new comment" />' + +      '<input name="post[title]" type="text" value="Hello World" />' \ +      '<input name="post[comments_attributes][0][name]" type="text" value="new comment" />' \        '<input name="post[comments_attributes][1][name]" type="text" value="new comment" />'      end @@ -1449,9 +1448,9 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/posts/123", method: "patch") do -      '<input name="post[title]" type="text" value="Hello World" />' + -      '<input name="post[comments_attributes][0][name]" type="text" value="comment #321" />' + -      '<input name="post[comments_attributes][0][id]" type="hidden" value="321" />' + +      '<input name="post[title]" type="text" value="Hello World" />' \ +      '<input name="post[comments_attributes][0][name]" type="text" value="comment #321" />' \ +      '<input name="post[comments_attributes][0][id]" type="hidden" value="321" />' \        '<input name="post[comments_attributes][1][name]" type="text" value="new comment" />'      end @@ -1484,10 +1483,10 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/posts/123", method: "patch") do -      '<input name="post[title]" type="text" value="Hello World" />' + -      '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + -      '<input name="post[comments_attributes][0][id]" type="hidden" value="1" />' + -      '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" />' + +      '<input name="post[title]" type="text" value="Hello World" />' \ +      '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" />' \ +      '<input name="post[comments_attributes][0][id]" type="hidden" value="1" />' \ +      '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" />' \        '<input name="post[comments_attributes][1][id]" type="hidden" value="2" />'      end @@ -1505,10 +1504,10 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/posts/123", method: "patch") do -      '<input name="post[title]" type="text" value="Hello World" />' + -      '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + -      '<input name="post[comments_attributes][0][id]" type="hidden" value="1" />' + -      '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" />' + +      '<input name="post[title]" type="text" value="Hello World" />' \ +      '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" />' \ +      '<input name="post[comments_attributes][0][id]" type="hidden" value="1" />' \ +      '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" />' \        '<input name="post[comments_attributes][1][id]" type="hidden" value="2" />'      end @@ -1540,10 +1539,10 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/posts/123", method: "patch") do -      '<input name="post[title]" type="text" value="Hello World" />' + -      '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + -      '<input name="post[comments_attributes][0][id]" type="hidden" value="1" />' + -      '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" />' + +      '<input name="post[title]" type="text" value="Hello World" />' \ +      '<input name="post[comments_attributes][0][name]" type="text" value="comment #1" />' \ +      '<input name="post[comments_attributes][0][id]" type="hidden" value="1" />' \ +      '<input name="post[comments_attributes][1][name]" type="text" value="comment #2" />' \        '<input name="post[comments_attributes][1][id]" type="hidden" value="2" />'      end @@ -1563,9 +1562,9 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/posts/123", method: "patch") do -      '<input name="post[title]" type="text" value="Hello World" />' + -      '<input name="post[comments_attributes][0][name]" type="text" value="comment #321" />' + -      '<input name="post[comments_attributes][0][id]" type="hidden" value="321" />' + +      '<input name="post[title]" type="text" value="Hello World" />' \ +      '<input name="post[comments_attributes][0][name]" type="text" value="comment #321" />' \ +      '<input name="post[comments_attributes][0][id]" type="hidden" value="321" />' \        '<input name="post[comments_attributes][1][name]" type="text" value="new comment" />'      end @@ -1583,7 +1582,7 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/posts/123", method: "patch") do -      '<input name="post[comments_attributes][abc][name]" type="text" value="comment #321" />' + +      '<input name="post[comments_attributes][abc][name]" type="text" value="comment #321" />' \        '<input name="post[comments_attributes][abc][id]" type="hidden" value="321" />'      end @@ -1600,7 +1599,7 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/posts/123", method: "patch") do -      '<input name="post[comments_attributes][abc][name]" type="text" value="comment #321" />' + +      '<input name="post[comments_attributes][abc][name]" type="text" value="comment #321" />' \        '<input name="post[comments_attributes][abc][id]" type="hidden" value="321" />'      end @@ -1623,7 +1622,7 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/posts/123", method: "patch") do -      '<input name="post[comments_attributes][abc][name]" type="text" value="comment #321" />' + +      '<input name="post[comments_attributes][abc][name]" type="text" value="comment #321" />' \        '<input name="post[comments_attributes][abc][id]" type="hidden" value="321" />'      end @@ -1709,17 +1708,17 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/posts/123", method: "patch") do -      '<input name="post[comments_attributes][0][name]" type="text" value="comment #321" />' + -      '<input name="post[comments_attributes][0][relevances_attributes][0][value]" type="text" value="commentrelevance #314" />' + -      '<input name="post[comments_attributes][0][relevances_attributes][0][id]" type="hidden" value="314" />' + -      '<input name="post[comments_attributes][0][id]" type="hidden" value="321" />' + -      '<input name="post[tags_attributes][0][value]" type="text" value="tag #123" />' + -      '<input name="post[tags_attributes][0][relevances_attributes][0][value]" type="text" value="tagrelevance #3141" />' + -      '<input name="post[tags_attributes][0][relevances_attributes][0][id]" type="hidden" value="3141" />' + -      '<input name="post[tags_attributes][0][id]" type="hidden" value="123" />' + -      '<input name="post[tags_attributes][1][value]" type="text" value="tag #456" />' + -      '<input name="post[tags_attributes][1][relevances_attributes][0][value]" type="text" value="tagrelevance #31415" />' + -      '<input name="post[tags_attributes][1][relevances_attributes][0][id]" type="hidden" value="31415" />' + +      '<input name="post[comments_attributes][0][name]" type="text" value="comment #321" />' \ +      '<input name="post[comments_attributes][0][relevances_attributes][0][value]" type="text" value="commentrelevance #314" />' \ +      '<input name="post[comments_attributes][0][relevances_attributes][0][id]" type="hidden" value="314" />' \ +      '<input name="post[comments_attributes][0][id]" type="hidden" value="321" />' \ +      '<input name="post[tags_attributes][0][value]" type="text" value="tag #123" />' \ +      '<input name="post[tags_attributes][0][relevances_attributes][0][value]" type="text" value="tagrelevance #3141" />' \ +      '<input name="post[tags_attributes][0][relevances_attributes][0][id]" type="hidden" value="3141" />' \ +      '<input name="post[tags_attributes][0][id]" type="hidden" value="123" />' \ +      '<input name="post[tags_attributes][1][value]" type="text" value="tag #456" />' \ +      '<input name="post[tags_attributes][1][relevances_attributes][0][value]" type="text" value="tagrelevance #31415" />' \ +      '<input name="post[tags_attributes][1][relevances_attributes][0][id]" type="hidden" value="31415" />' \        '<input name="post[tags_attributes][1][id]" type="hidden" value="456" />'      end @@ -1750,9 +1749,9 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = -      "<input name='post[title]' type='text' value='Hello World' />" + -      "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" + -      "<input name='post[secret]' type='hidden' value='0' />" + +      "<input name='post[title]' type='text' value='Hello World' />" \ +      "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" \ +      "<input name='post[secret]' type='hidden' value='0' />" \        "<input name='post[secret]' checked='checked' type='checkbox' value='1' />"      assert_dom_equal expected, output_buffer @@ -1766,9 +1765,9 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = -      "<input name='post[123][title]' type='text' value='Hello World' />" + -      "<textarea name='post[123][body]'>\nBack to the hill and over it again!</textarea>" + -      "<input name='post[123][secret]' type='hidden' value='0' />" + +      "<input name='post[123][title]' type='text' value='Hello World' />" \ +      "<textarea name='post[123][body]'>\nBack to the hill and over it again!</textarea>" \ +      "<input name='post[123][secret]' type='hidden' value='0' />" \        "<input name='post[123][secret]' checked='checked' type='checkbox' value='1' />"      assert_dom_equal expected, output_buffer @@ -1782,9 +1781,9 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = -      "<input name='post[][title]' type='text' value='Hello World' />" + -      "<textarea name='post[][body]'>\nBack to the hill and over it again!</textarea>" + -      "<input name='post[][secret]' type='hidden' value='0' />" + +      "<input name='post[][title]' type='text' value='Hello World' />" \ +      "<textarea name='post[][body]'>\nBack to the hill and over it again!</textarea>" \ +      "<input name='post[][secret]' type='hidden' value='0' />" \        "<input name='post[][secret]' checked='checked' type='checkbox' value='1' />"      assert_dom_equal expected, output_buffer @@ -1798,9 +1797,9 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = -      "<input name='post[abc][title]' type='text' value='Hello World' />" + -      "<textarea name='post[abc][body]'>\nBack to the hill and over it again!</textarea>" + -      "<input name='post[abc][secret]' type='hidden' value='0' />" + +      "<input name='post[abc][title]' type='text' value='Hello World' />" \ +      "<textarea name='post[abc][body]'>\nBack to the hill and over it again!</textarea>" \ +      "<input name='post[abc][secret]' type='hidden' value='0' />" \        "<input name='post[abc][secret]' checked='checked' type='checkbox' value='1' />"      assert_dom_equal expected, output_buffer @@ -1814,9 +1813,9 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = -      "<input name='post[title]' type='text' value='Hello World' />" + -      "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" + -      "<input name='post[secret]' type='hidden' value='0' />" + +      "<input name='post[title]' type='text' value='Hello World' />" \ +      "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" \ +      "<input name='post[secret]' type='hidden' value='0' />" \        "<input name='post[secret]' checked='checked' type='checkbox' value='1' />"      assert_dom_equal expected, output_buffer @@ -1830,9 +1829,9 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = -      "<input name='post[title]' type='text' value='Hello World' />" + -      "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" + -      "<input name='post[secret]' type='hidden' value='0' />" + +      "<input name='post[title]' type='text' value='Hello World' />" \ +      "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" \ +      "<input name='post[secret]' type='hidden' value='0' />" \        "<input name='post[secret]' checked='checked' type='checkbox' value='1' />"      assert_dom_equal expected, output_buffer @@ -1844,7 +1843,7 @@ class FormWithActsLikeFormForTest < FormWithTest        concat f.text_field(:title)      end -    assert_dom_equal "<label for=\"author_post_title\">Title</label>" + +    assert_dom_equal "<label for=\"author_post_title\">Title</label>" \      "<input name='author[post][title]' type='text' value='Hello World' />",        output_buffer    end @@ -1855,7 +1854,7 @@ class FormWithActsLikeFormForTest < FormWithTest        concat f.text_field(:title)      end -    assert_dom_equal "<label for=\"author_post_1_title\">Title</label>" + +    assert_dom_equal "<label for=\"author_post_1_title\">Title</label>" \        "<input name='author[post][1][title]' type='text' value='Hello World' />",        output_buffer    end @@ -1875,9 +1874,9 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/posts/123", "create-post", method: "patch") do -      "<input name='post[title]' type='text' value='Hello World' />" + -      "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" + -      "<input name='parent_post[secret]' type='hidden' value='0' />" + +      "<input name='post[title]' type='text' value='Hello World' />" \ +      "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" \ +      "<input name='parent_post[secret]' type='hidden' value='0' />" \        "<input name='parent_post[secret]' checked='checked' type='checkbox' value='1' />"      end @@ -1895,8 +1894,8 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/posts/123", "create-post", method: "patch") do -      "<input name='post[title]' type='text' value='Hello World' />" + -      "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" + +      "<input name='post[title]' type='text' value='Hello World' />" \ +      "<textarea name='post[body]'>\nBack to the hill and over it again!</textarea>" \        "<input name='post[comment][name]' type='text' value='new comment' />"      end @@ -1935,8 +1934,8 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/posts/123", method: "patch") do -      "<label for='title'>Title:</label> <input name='post[title]' type='text' value='Hello World' /><br/>" + -      "<label for='body'>Body:</label> <textarea name='post[body]'>\nBack to the hill and over it again!</textarea><br/>" + +      "<label for='title'>Title:</label> <input name='post[title]' type='text' value='Hello World' /><br/>" \ +      "<label for='body'>Body:</label> <textarea name='post[body]'>\nBack to the hill and over it again!</textarea><br/>" \        "<label for='secret'>Secret:</label> <input name='post[secret]' type='hidden' value='0' /><input name='post[secret]' checked='checked' type='checkbox' value='1' /><br/>"      end @@ -1954,8 +1953,8 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = whole_form("/posts/123", method: "patch") do -      "<label for='title'>Title:</label> <input name='post[title]' type='text' value='Hello World' /><br/>" + -      "<label for='body'>Body:</label> <textarea name='post[body]'>\nBack to the hill and over it again!</textarea><br/>" + +      "<label for='title'>Title:</label> <input name='post[title]' type='text' value='Hello World' /><br/>" \ +      "<label for='body'>Body:</label> <textarea name='post[body]'>\nBack to the hill and over it again!</textarea><br/>" \        "<label for='secret'>Secret:</label> <input name='post[secret]' type='hidden' value='0' /><input name='post[secret]' checked='checked' type='checkbox' value='1' /><br/>"      end @@ -2013,8 +2012,8 @@ class FormWithActsLikeFormForTest < FormWithTest      end      expected = -      "<label for='title'>Title:</label> <input name='post[title]' type='text' value='Hello World' /><br/>" + -      "<label for='body'>Body:</label> <textarea name='post[body]'>\nBack to the hill and over it again!</textarea><br/>" + +      "<label for='title'>Title:</label> <input name='post[title]' type='text' value='Hello World' /><br/>" \ +      "<label for='body'>Body:</label> <textarea name='post[body]'>\nBack to the hill and over it again!</textarea><br/>" \        "<label for='secret'>Secret:</label> <input name='post[secret]' type='hidden' value='0' /><input name='post[secret]' checked='checked' type='checkbox' value='1' /><br/>"      assert_dom_equal expected, output_buffer diff --git a/actionview/test/template/form_helper_test.rb b/actionview/test/template/form_helper_test.rb index 4850bc390b..83231d5277 100644 --- a/actionview/test/template/form_helper_test.rb +++ b/actionview/test/template/form_helper_test.rb @@ -1515,13 +1515,13 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts/123", "create-post", "edit_post", method: "patch") do -      "<label for='post_title'>The Title</label>" + -      "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + -      "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" + -      "<input name='post[secret]' type='hidden' value='0' />" + -      "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" + -      "<input name='commit' data-disable-with='Create post' type='submit' value='Create post' />" + -      "<button name='button' type='submit'>Create post</button>" + +      "<label for='post_title'>The Title</label>" \ +      "<input name='post[title]' type='text' id='post_title' value='Hello World' />" \ +      "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" \ +      "<input name='post[secret]' type='hidden' value='0' />" \ +      "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" \ +      "<input name='commit' data-disable-with='Create post' type='submit' value='Create post' />" \ +      "<button name='button' type='submit'>Create post</button>" \        "<button name='button' type='submit'><span>Create post</span></button>"      end @@ -1536,10 +1536,10 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts", "new_post", "new_post") do -      "<input type='hidden' name='post[active]' value='' />" + -      "<input id='post_active_true' name='post[active]' type='radio' value='true' />" + -      "<label for='post_active_true'>true</label>" + -      "<input checked='checked' id='post_active_false' name='post[active]' type='radio' value='false' />" + +      "<input type='hidden' name='post[active]' value='' />" \ +      "<input id='post_active_true' name='post[active]' type='radio' value='true' />" \ +      "<label for='post_active_true'>true</label>" \ +      "<input checked='checked' id='post_active_false' name='post[active]' type='radio' value='false' />" \        "<label for='post_active_false'>false</label>"      end @@ -1558,12 +1558,12 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts", "new_post", "new_post") do -      "<input type='hidden' name='post[active]' value='' />" + -      "<label for='post_active_true'>" + -      "<input id='post_active_true' name='post[active]' type='radio' value='true' />" + -      "true</label>" + -      "<label for='post_active_false'>" + -      "<input checked='checked' id='post_active_false' name='post[active]' type='radio' value='false' />" + +      "<input type='hidden' name='post[active]' value='' />" \ +      "<label for='post_active_true'>" \ +      "<input id='post_active_true' name='post[active]' type='radio' value='true' />" \ +      "true</label>" \ +      "<label for='post_active_false'>" \ +      "<input checked='checked' id='post_active_false' name='post[active]' type='radio' value='false' />" \        "false</label>"      end @@ -1584,13 +1584,13 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts", "new_post_1", "new_post") do -      "<input type='hidden' name='post[active]' value='' />" + -      "<label for='post_active_true'>" + -      "<input id='post_active_true' name='post[active]' type='radio' value='true' />" + -      "true</label>" + -      "<label for='post_active_false'>" + -      "<input checked='checked' id='post_active_false' name='post[active]' type='radio' value='false' />" + -      "false</label>" + +      "<input type='hidden' name='post[active]' value='' />" \ +      "<label for='post_active_true'>" \ +      "<input id='post_active_true' name='post[active]' type='radio' value='true' />" \ +      "true</label>" \ +      "<label for='post_active_false'>" \ +      "<input checked='checked' id='post_active_false' name='post[active]' type='radio' value='false' />" \ +      "false</label>" \        "<input id='post_id' name='post[id]' type='hidden' value='1' />"      end @@ -1606,10 +1606,10 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts", "foo_new_post", "new_post") do -      "<input type='hidden' name='post[active]' value='' />" + -      "<input id='foo_post_active_true' name='post[active]' type='radio' value='true' />" + -      "<label for='foo_post_active_true'>true</label>" + -      "<input checked='checked' id='foo_post_active_false' name='post[active]' type='radio' value='false' />" + +      "<input type='hidden' name='post[active]' value='' />" \ +      "<input id='foo_post_active_true' name='post[active]' type='radio' value='true' />" \ +      "<label for='foo_post_active_true'>true</label>" \ +      "<input checked='checked' id='foo_post_active_false' name='post[active]' type='radio' value='false' />" \        "<label for='foo_post_active_false'>false</label>"      end @@ -1625,10 +1625,10 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts", "new_post", "new_post") do -      "<input type='hidden' name='post[1][active]' value='' />" + -      "<input id='post_1_active_true' name='post[1][active]' type='radio' value='true' />" + -      "<label for='post_1_active_true'>true</label>" + -      "<input checked='checked' id='post_1_active_false' name='post[1][active]' type='radio' value='false' />" + +      "<input type='hidden' name='post[1][active]' value='' />" \ +      "<input id='post_1_active_true' name='post[1][active]' type='radio' value='true' />" \ +      "<label for='post_1_active_true'>true</label>" \ +      "<input checked='checked' id='post_1_active_false' name='post[1][active]' type='radio' value='false' />" \        "<label for='post_1_active_false'>false</label>"      end @@ -1644,12 +1644,12 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts", "new_post", "new_post") do -      "<input name='post[tag_ids][]' type='hidden' value='' />" + -      "<input checked='checked' id='post_tag_ids_1' name='post[tag_ids][]' type='checkbox' value='1' />" + -      "<label for='post_tag_ids_1'>Tag 1</label>" + -      "<input id='post_tag_ids_2' name='post[tag_ids][]' type='checkbox' value='2' />" + -      "<label for='post_tag_ids_2'>Tag 2</label>" + -      "<input checked='checked' id='post_tag_ids_3' name='post[tag_ids][]' type='checkbox' value='3' />" + +      "<input name='post[tag_ids][]' type='hidden' value='' />" \ +      "<input checked='checked' id='post_tag_ids_1' name='post[tag_ids][]' type='checkbox' value='1' />" \ +      "<label for='post_tag_ids_1'>Tag 1</label>" \ +      "<input id='post_tag_ids_2' name='post[tag_ids][]' type='checkbox' value='2' />" \ +      "<label for='post_tag_ids_2'>Tag 2</label>" \ +      "<input checked='checked' id='post_tag_ids_3' name='post[tag_ids][]' type='checkbox' value='3' />" \        "<label for='post_tag_ids_3'>Tag 3</label>"      end @@ -1668,15 +1668,15 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts", "new_post", "new_post") do -      "<input name='post[tag_ids][]' type='hidden' value='' />" + -      "<label for='post_tag_ids_1'>" + -      "<input checked='checked' id='post_tag_ids_1' name='post[tag_ids][]' type='checkbox' value='1' />" + -      "Tag 1</label>" + -      "<label for='post_tag_ids_2'>" + -      "<input id='post_tag_ids_2' name='post[tag_ids][]' type='checkbox' value='2' />" + -      "Tag 2</label>" + -      "<label for='post_tag_ids_3'>" + -      "<input checked='checked' id='post_tag_ids_3' name='post[tag_ids][]' type='checkbox' value='3' />" + +      "<input name='post[tag_ids][]' type='hidden' value='' />" \ +      "<label for='post_tag_ids_1'>" \ +      "<input checked='checked' id='post_tag_ids_1' name='post[tag_ids][]' type='checkbox' value='1' />" \ +      "Tag 1</label>" \ +      "<label for='post_tag_ids_2'>" \ +      "<input id='post_tag_ids_2' name='post[tag_ids][]' type='checkbox' value='2' />" \ +      "Tag 2</label>" \ +      "<label for='post_tag_ids_3'>" \ +      "<input checked='checked' id='post_tag_ids_3' name='post[tag_ids][]' type='checkbox' value='3' />" \        "Tag 3</label>"      end @@ -1698,16 +1698,16 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts", "new_post_1", "new_post") do -      "<input name='post[tag_ids][]' type='hidden' value='' />" + -      "<label for='post_tag_ids_1'>" + -      "<input checked='checked' id='post_tag_ids_1' name='post[tag_ids][]' type='checkbox' value='1' />" + -      "Tag 1</label>" + -      "<label for='post_tag_ids_2'>" + -      "<input id='post_tag_ids_2' name='post[tag_ids][]' type='checkbox' value='2' />" + -      "Tag 2</label>" + -      "<label for='post_tag_ids_3'>" + -      "<input checked='checked' id='post_tag_ids_3' name='post[tag_ids][]' type='checkbox' value='3' />" + -      "Tag 3</label>" + +      "<input name='post[tag_ids][]' type='hidden' value='' />" \ +      "<label for='post_tag_ids_1'>" \ +      "<input checked='checked' id='post_tag_ids_1' name='post[tag_ids][]' type='checkbox' value='1' />" \ +      "Tag 1</label>" \ +      "<label for='post_tag_ids_2'>" \ +      "<input id='post_tag_ids_2' name='post[tag_ids][]' type='checkbox' value='2' />" \ +      "Tag 2</label>" \ +      "<label for='post_tag_ids_3'>" \ +      "<input checked='checked' id='post_tag_ids_3' name='post[tag_ids][]' type='checkbox' value='3' />" \ +      "Tag 3</label>" \        "<input id='post_id' name='post[id]' type='hidden' value='1' />"      end @@ -1724,8 +1724,8 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts", "foo_new_post", "new_post") do -      "<input name='post[tag_ids][]' type='hidden' value='' />" + -      "<input checked='checked' id='foo_post_tag_ids_1' name='post[tag_ids][]' type='checkbox' value='1' />" + +      "<input name='post[tag_ids][]' type='hidden' value='' />" \ +      "<input checked='checked' id='foo_post_tag_ids_1' name='post[tag_ids][]' type='checkbox' value='1' />" \        "<label for='foo_post_tag_ids_1'>Tag 1</label>"      end @@ -1742,8 +1742,8 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts", "new_post", "new_post") do -      "<input name='post[1][tag_ids][]' type='hidden' value='' />" + -      "<input checked='checked' id='post_1_tag_ids_1' name='post[1][tag_ids][]' type='checkbox' value='1' />" + +      "<input name='post[1][tag_ids][]' type='hidden' value='' />" \ +      "<input checked='checked' id='post_1_tag_ids_1' name='post[1][tag_ids][]' type='checkbox' value='1' />" \        "<label for='post_1_tag_ids_1'>Tag 1</label>"      end @@ -1797,7 +1797,7 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts/44", "edit_post_44", "edit_post", method: "patch") do -      "<input name='post[title]' type='text' id='post_title' value='And his name will be forty and four.' />" + +      "<input name='post[title]' type='text' id='post_title' value='And his name will be forty and four.' />" \        "<input name='commit' data-disable-with='Edit post' type='submit' value='Edit post' />"      end @@ -1814,11 +1814,11 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts/123", "create-post", "edit_other_name", method: "patch") do -      "<label for='other_name_title' class='post_title'>Title</label>" + -      "<input name='other_name[title]' id='other_name_title' value='Hello World' type='text' />" + -      "<textarea name='other_name[body]' id='other_name_body'>\nBack to the hill and over it again!</textarea>" + -      "<input name='other_name[secret]' value='0' type='hidden' />" + -      "<input name='other_name[secret]' checked='checked' id='other_name_secret' value='1' type='checkbox' />" + +      "<label for='other_name_title' class='post_title'>Title</label>" \ +      "<input name='other_name[title]' id='other_name_title' value='Hello World' type='text' />" \ +      "<textarea name='other_name[body]' id='other_name_body'>\nBack to the hill and over it again!</textarea>" \ +      "<input name='other_name[secret]' value='0' type='hidden' />" \ +      "<input name='other_name[secret]' checked='checked' id='other_name_secret' value='1' type='checkbox' />" \        "<input name='commit' value='Create post' data-disable-with='Create post' type='submit' />"      end @@ -1829,9 +1829,9 @@ class FormHelperTest < ActionView::TestCase      obj = Class.new do        private -      def private_property -        raise "This method should not be called." -      end +        def private_property +          raise "This method should not be called." +        end      end.new      form_for(obj, as: "other_name", url: "/", html: { id: "edit-other-name" }) do |f| @@ -1847,9 +1847,9 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/", "create-post", "edit_post", method: "delete") do -      "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + -      "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" + -      "<input name='post[secret]' type='hidden' value='0' />" + +      "<input name='post[title]' type='text' id='post_title' value='Hello World' />" \ +      "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" \ +      "<input name='post[secret]' type='hidden' value='0' />" \        "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />"      end @@ -1864,9 +1864,9 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/", "create-post", "edit_post", method: "delete") do -      "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + -      "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" + -      "<input name='post[secret]' type='hidden' value='0' />" + +      "<input name='post[title]' type='text' id='post_title' value='Hello World' />" \ +      "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" \ +      "<input name='post[secret]' type='hidden' value='0' />" \        "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />"      end @@ -1895,9 +1895,9 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/", "create-post", "edit_post", method: "patch", remote: true) do -      "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + -      "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" + -      "<input name='post[secret]' type='hidden' value='0' />" + +      "<input name='post[title]' type='text' id='post_title' value='Hello World' />" \ +      "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" \ +      "<input name='post[secret]' type='hidden' value='0' />" \        "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />"      end @@ -1936,9 +1936,9 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/", "create-post", "edit_post", method: "patch", remote: true) do -      "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + -      "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" + -      "<input name='post[secret]' type='hidden' value='0' />" + +      "<input name='post[title]' type='text' id='post_title' value='Hello World' />" \ +      "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" \ +      "<input name='post[secret]' type='hidden' value='0' />" \        "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />"      end @@ -1955,9 +1955,9 @@ class FormHelperTest < ActionView::TestCase        end        expected = whole_form("/posts", "new_post", "new_post", remote: true) do -        "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + -        "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" + -        "<input name='post[secret]' type='hidden' value='0' />" + +        "<input name='post[title]' type='text' id='post_title' value='Hello World' />" \ +        "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" \ +        "<input name='post[secret]' type='hidden' value='0' />" \          "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />"        end @@ -1973,9 +1973,9 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/", "create-post") do -      "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + -      "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" + -      "<input name='post[secret]' type='hidden' value='0' />" + +      "<input name='post[title]' type='text' id='post_title' value='Hello World' />" \ +      "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" \ +      "<input name='post[secret]' type='hidden' value='0' />" \        "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />"      end @@ -1991,10 +1991,10 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts/123", "edit_post[]", "edit_post[]", method: "patch") do -      "<label for='post_123_title'>Title</label>" + -      "<input name='post[123][title]' type='text' id='post_123_title' value='Hello World' />" + -      "<textarea name='post[123][body]' id='post_123_body'>\nBack to the hill and over it again!</textarea>" + -      "<input name='post[123][secret]' type='hidden' value='0' />" + +      "<label for='post_123_title'>Title</label>" \ +      "<input name='post[123][title]' type='text' id='post_123_title' value='Hello World' />" \ +      "<textarea name='post[123][body]' id='post_123_body'>\nBack to the hill and over it again!</textarea>" \ +      "<input name='post[123][secret]' type='hidden' value='0' />" \        "<input name='post[123][secret]' checked='checked' type='checkbox' id='post_123_secret' value='1' />"      end @@ -2009,9 +2009,9 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts/123", "edit_post[]", "edit_post[]", method: "patch") do -      "<input name='post[][title]' type='text' id='post__title' value='Hello World' />" + -      "<textarea name='post[][body]' id='post__body'>\nBack to the hill and over it again!</textarea>" + -      "<input name='post[][secret]' type='hidden' value='0' />" + +      "<input name='post[][title]' type='text' id='post__title' value='Hello World' />" \ +      "<textarea name='post[][body]' id='post__body'>\nBack to the hill and over it again!</textarea>" \ +      "<input name='post[][secret]' type='hidden' value='0' />" \        "<input name='post[][secret]' checked='checked' type='checkbox' id='post__secret' value='1' />"      end @@ -2026,8 +2026,8 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do -      "<div class='field_with_errors'><label for='post_author_name' class='label'>Author name</label></div>" + -      "<div class='field_with_errors'><input name='post[author_name]' type='text' id='post_author_name' value='' /></div>" + +      "<div class='field_with_errors'><label for='post_author_name' class='label'>Author name</label></div>" \ +      "<div class='field_with_errors'><input name='post[author_name]' type='text' id='post_author_name' value='' /></div>" \        "<input name='commit' data-disable-with='Create post' type='submit' value='Create post' />"      end @@ -2044,8 +2044,8 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do -      "<div class='field_with_errors'><label for='post_author_name' class='label'>Author name</label></div>" + -      "<div class='field_with_errors'><input name='post[author_name]' type='text' id='post_author_name' value='' /></div>" + +      "<div class='field_with_errors'><label for='post_author_name' class='label'>Author name</label></div>" \ +      "<div class='field_with_errors'><input name='post[author_name]' type='text' id='post_author_name' value='' /></div>" \        "<input name='commit' data-disable-with='Create post' type='submit' value='Create post' />"      end @@ -2059,7 +2059,7 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do -      "<div class='field_with_errors'><label for='post_author_name' class='label'>Name</label></div>" + +      "<div class='field_with_errors'><label for='post_author_name' class='label'>Name</label></div>" \        "<div class='field_with_errors'><label for='post_author_name' class='label'>Name</label></div>"      end @@ -2074,9 +2074,9 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts/123", "namespace_edit_post_123", "edit_post", method: "patch") do -      "<input name='post[title]' type='text' id='namespace_post_title' value='Hello World' />" + -      "<textarea name='post[body]' id='namespace_post_body'>\nBack to the hill and over it again!</textarea>" + -      "<input name='post[secret]' type='hidden' value='0' />" + +      "<input name='post[title]' type='text' id='namespace_post_title' value='Hello World' />" \ +      "<textarea name='post[body]' id='namespace_post_body'>\nBack to the hill and over it again!</textarea>" \ +      "<input name='post[secret]' type='hidden' value='0' />" \        "<input name='post[secret]' checked='checked' type='checkbox' id='namespace_post_secret' value='1' />"      end @@ -2098,7 +2098,7 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts/123", "namespace_edit_post_123", "edit_post", method: "patch") do -      "<label for='namespace_post_title'>Title</label>" + +      "<label for='namespace_post_title'>Title</label>" \        "<input name='post[title]' type='text' id='namespace_post_title' value='Hello World' />"      end @@ -2124,7 +2124,7 @@ class FormHelperTest < ActionView::TestCase      end      expected_1 = whole_form("/posts/123", "namespace_1_edit_post_123", "edit_post", method: "patch") do -      "<label for='namespace_1_post_title'>Title</label>" + +      "<label for='namespace_1_post_title'>Title</label>" \        "<input name='post[title]' type='text' id='namespace_1_post_title' value='Hello World' />"      end @@ -2136,7 +2136,7 @@ class FormHelperTest < ActionView::TestCase      end      expected_2 = whole_form("/posts/123", "namespace_2_edit_post_123", "edit_post", method: "patch") do -      "<label for='namespace_2_post_title'>Title</label>" + +      "<label for='namespace_2_post_title'>Title</label>" \        "<input name='post[title]' type='text' id='namespace_2_post_title' value='Hello World' />"      end @@ -2154,8 +2154,8 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts/123", "namespace_edit_post_123", "edit_post", method: "patch") do -      "<input name='post[title]' type='text' id='namespace_post_title' value='Hello World' />" + -      "<textarea name='post[body]' id='namespace_post_body'>\nBack to the hill and over it again!</textarea>" + +      "<input name='post[title]' type='text' id='namespace_post_title' value='Hello World' />" \ +      "<textarea name='post[body]' id='namespace_post_body'>\nBack to the hill and over it again!</textarea>" \        "<input name='post[comment][body]' type='text' id='namespace_post_comment_body' value='Hello World' />"      end @@ -2262,11 +2262,13 @@ class FormHelperTest < ActionView::TestCase        concat f.fields_for("comment[]", @comment) { |c|          concat c.text_field(:name)        } +      concat f.text_field(:body)      end      expected = whole_form("/posts/123", "edit_post[]", "edit_post[]", method: "patch") do -      "<input name='post[123][title]' type='text' id='post_123_title' value='Hello World' />" + -      "<input name='post[123][comment][][name]' type='text' id='post_123_comment__name' value='new comment' />" +      "<input name='post[123][title]' type='text' id='post_123_title' value='Hello World' />" \ +      "<input name='post[123][comment][][name]' type='text' id='post_123_comment__name' value='new comment' />" \ +      "<input name='post[123][body]' type='text' id='post_123_body' value='Back to the hill and over it again!' />"      end      assert_dom_equal expected, output_buffer @@ -2281,7 +2283,7 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do -      "<input name='post[1][title]' type='text' id='post_1_title' value='Hello World' />" + +      "<input name='post[1][title]' type='text' id='post_1_title' value='Hello World' />" \        "<input name='post[1][comment][1][name]' type='text' id='post_1_comment_1_name' value='new comment' />"      end @@ -2391,7 +2393,7 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do -      '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + +      '<input name="post[title]" type="text" id="post_title" value="Hello World" />' \          '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="new author" />'      end @@ -2418,8 +2420,8 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do -      '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + -      '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' + +      '<input name="post[title]" type="text" id="post_title" value="Hello World" />' \ +      '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' \        '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />'      end @@ -2437,8 +2439,8 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do -      '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + -      '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' + +      '<input name="post[title]" type="text" id="post_title" value="Hello World" />' \ +      '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' \        '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />'      end @@ -2456,7 +2458,7 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do -      '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + +      '<input name="post[title]" type="text" id="post_title" value="Hello World" />' \        '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />'      end @@ -2474,7 +2476,7 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do -      '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + +      '<input name="post[title]" type="text" id="post_title" value="Hello World" />' \        '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />'      end @@ -2492,8 +2494,8 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do -      '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + -      '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' + +      '<input name="post[title]" type="text" id="post_title" value="Hello World" />' \ +      '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' \        '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />'      end @@ -2512,8 +2514,8 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do -      '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + -      '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' + +      '<input name="post[title]" type="text" id="post_title" value="Hello World" />' \ +      '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' \        '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />'      end @@ -2533,10 +2535,10 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do -      '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + -      '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + -      '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' + -      '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />' + +      '<input name="post[title]" type="text" id="post_title" value="Hello World" />' \ +      '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' \ +      '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' \ +      '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />' \        '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />'      end @@ -2560,10 +2562,10 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do -      '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + -      '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' + -      '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' + -      '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + +      '<input name="post[title]" type="text" id="post_title" value="Hello World" />' \ +      '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' \ +      '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' \ +      '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' \        '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />'      end @@ -2587,9 +2589,9 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do -      '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + -      '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' + -      '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + +      '<input name="post[title]" type="text" id="post_title" value="Hello World" />' \ +      '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' \ +      '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' \        '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />'      end @@ -2613,10 +2615,10 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do -      '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + -      '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' + -      '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' + -      '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + +      '<input name="post[title]" type="text" id="post_title" value="Hello World" />' \ +      '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' \ +      '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' \ +      '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' \        '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />'      end @@ -2636,10 +2638,10 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do -      '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + -      '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + -      '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' + -      '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />' + +      '<input name="post[title]" type="text" id="post_title" value="Hello World" />' \ +      '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' \ +      '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' \ +      '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />' \        '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />'      end @@ -2660,10 +2662,10 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do -      '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + -      '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' + -      '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + -      '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' + +      '<input name="post[title]" type="text" id="post_title" value="Hello World" />' \ +      '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' \ +      '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' \ +      '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' \        '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />'      end @@ -2683,8 +2685,8 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do -      '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + -      '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="new comment" />' + +      '<input name="post[title]" type="text" id="post_title" value="Hello World" />' \ +      '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="new comment" />' \        '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="new comment" />'      end @@ -2704,9 +2706,9 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do -      '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + -      '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #321" />' + -      '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' + +      '<input name="post[title]" type="text" id="post_title" value="Hello World" />' \ +      '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #321" />' \ +      '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' \        '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="new comment" />'      end @@ -2739,10 +2741,10 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do -      '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + -      '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + -      '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' + -      '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />' + +      '<input name="post[title]" type="text" id="post_title" value="Hello World" />' \ +      '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' \ +      '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' \ +      '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />' \        '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />'      end @@ -2760,10 +2762,10 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do -      '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + -      '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + -      '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' + -      '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />' + +      '<input name="post[title]" type="text" id="post_title" value="Hello World" />' \ +      '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' \ +      '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' \ +      '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />' \        '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />'      end @@ -2795,10 +2797,10 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do -      '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + -      '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + -      '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' + -      '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />' + +      '<input name="post[title]" type="text" id="post_title" value="Hello World" />' \ +      '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' \ +      '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' \ +      '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />' \        '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />'      end @@ -2818,9 +2820,9 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do -      '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + -      '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #321" />' + -      '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' + +      '<input name="post[title]" type="text" id="post_title" value="Hello World" />' \ +      '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #321" />' \ +      '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' \        '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="new comment" />'      end @@ -2838,7 +2840,7 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do -      '<input id="post_comments_attributes_abc_name" name="post[comments_attributes][abc][name]" type="text" value="comment #321" />' + +      '<input id="post_comments_attributes_abc_name" name="post[comments_attributes][abc][name]" type="text" value="comment #321" />' \        '<input id="post_comments_attributes_abc_id" name="post[comments_attributes][abc][id]" type="hidden" value="321" />'      end @@ -2855,7 +2857,7 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do -      '<input id="post_comments_attributes_abc_name" name="post[comments_attributes][abc][name]" type="text" value="comment #321" />' + +      '<input id="post_comments_attributes_abc_name" name="post[comments_attributes][abc][name]" type="text" value="comment #321" />' \        '<input id="post_comments_attributes_abc_id" name="post[comments_attributes][abc][id]" type="hidden" value="321" />'      end @@ -2878,7 +2880,7 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do -      '<input id="post_comments_attributes_abc_name" name="post[comments_attributes][abc][name]" type="text" value="comment #321" />' + +      '<input id="post_comments_attributes_abc_name" name="post[comments_attributes][abc][name]" type="text" value="comment #321" />' \        '<input id="post_comments_attributes_abc_id" name="post[comments_attributes][abc][id]" type="hidden" value="321" />'      end @@ -2964,17 +2966,17 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do -      '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #321" />' + -      '<input id="post_comments_attributes_0_relevances_attributes_0_value" name="post[comments_attributes][0][relevances_attributes][0][value]" type="text" value="commentrelevance #314" />' + -      '<input id="post_comments_attributes_0_relevances_attributes_0_id" name="post[comments_attributes][0][relevances_attributes][0][id]" type="hidden" value="314" />' + -      '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' + -      '<input id="post_tags_attributes_0_value" name="post[tags_attributes][0][value]" type="text" value="tag #123" />' + -      '<input id="post_tags_attributes_0_relevances_attributes_0_value" name="post[tags_attributes][0][relevances_attributes][0][value]" type="text" value="tagrelevance #3141" />' + -      '<input id="post_tags_attributes_0_relevances_attributes_0_id" name="post[tags_attributes][0][relevances_attributes][0][id]" type="hidden" value="3141" />' + -      '<input id="post_tags_attributes_0_id" name="post[tags_attributes][0][id]" type="hidden" value="123" />' + -      '<input id="post_tags_attributes_1_value" name="post[tags_attributes][1][value]" type="text" value="tag #456" />' + -      '<input id="post_tags_attributes_1_relevances_attributes_0_value" name="post[tags_attributes][1][relevances_attributes][0][value]" type="text" value="tagrelevance #31415" />' + -      '<input id="post_tags_attributes_1_relevances_attributes_0_id" name="post[tags_attributes][1][relevances_attributes][0][id]" type="hidden" value="31415" />' + +      '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #321" />' \ +      '<input id="post_comments_attributes_0_relevances_attributes_0_value" name="post[comments_attributes][0][relevances_attributes][0][value]" type="text" value="commentrelevance #314" />' \ +      '<input id="post_comments_attributes_0_relevances_attributes_0_id" name="post[comments_attributes][0][relevances_attributes][0][id]" type="hidden" value="314" />' \ +      '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' \ +      '<input id="post_tags_attributes_0_value" name="post[tags_attributes][0][value]" type="text" value="tag #123" />' \ +      '<input id="post_tags_attributes_0_relevances_attributes_0_value" name="post[tags_attributes][0][relevances_attributes][0][value]" type="text" value="tagrelevance #3141" />' \ +      '<input id="post_tags_attributes_0_relevances_attributes_0_id" name="post[tags_attributes][0][relevances_attributes][0][id]" type="hidden" value="3141" />' \ +      '<input id="post_tags_attributes_0_id" name="post[tags_attributes][0][id]" type="hidden" value="123" />' \ +      '<input id="post_tags_attributes_1_value" name="post[tags_attributes][1][value]" type="text" value="tag #456" />' \ +      '<input id="post_tags_attributes_1_relevances_attributes_0_value" name="post[tags_attributes][1][relevances_attributes][0][value]" type="text" value="tagrelevance #31415" />' \ +      '<input id="post_tags_attributes_1_relevances_attributes_0_id" name="post[tags_attributes][1][relevances_attributes][0][id]" type="hidden" value="31415" />' \        '<input id="post_tags_attributes_1_id" name="post[tags_attributes][1][id]" type="hidden" value="456" />'      end @@ -3005,9 +3007,9 @@ class FormHelperTest < ActionView::TestCase      end      expected = -      "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + -      "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" + -      "<input name='post[secret]' type='hidden' value='0' />" + +      "<input name='post[title]' type='text' id='post_title' value='Hello World' />" \ +      "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" \ +      "<input name='post[secret]' type='hidden' value='0' />" \        "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />"      assert_dom_equal expected, output_buffer @@ -3021,9 +3023,9 @@ class FormHelperTest < ActionView::TestCase      end      expected = -      "<input name='post[123][title]' type='text' id='post_123_title' value='Hello World' />" + -      "<textarea name='post[123][body]' id='post_123_body'>\nBack to the hill and over it again!</textarea>" + -      "<input name='post[123][secret]' type='hidden' value='0' />" + +      "<input name='post[123][title]' type='text' id='post_123_title' value='Hello World' />" \ +      "<textarea name='post[123][body]' id='post_123_body'>\nBack to the hill and over it again!</textarea>" \ +      "<input name='post[123][secret]' type='hidden' value='0' />" \        "<input name='post[123][secret]' checked='checked' type='checkbox' id='post_123_secret' value='1' />"      assert_dom_equal expected, output_buffer @@ -3037,9 +3039,9 @@ class FormHelperTest < ActionView::TestCase      end      expected = -      "<input name='post[][title]' type='text' id='post__title' value='Hello World' />" + -      "<textarea name='post[][body]' id='post__body'>\nBack to the hill and over it again!</textarea>" + -      "<input name='post[][secret]' type='hidden' value='0' />" + +      "<input name='post[][title]' type='text' id='post__title' value='Hello World' />" \ +      "<textarea name='post[][body]' id='post__body'>\nBack to the hill and over it again!</textarea>" \ +      "<input name='post[][secret]' type='hidden' value='0' />" \        "<input name='post[][secret]' checked='checked' type='checkbox' id='post__secret' value='1' />"      assert_dom_equal expected, output_buffer @@ -3053,9 +3055,9 @@ class FormHelperTest < ActionView::TestCase      end      expected = -      "<input name='post[abc][title]' type='text' id='post_abc_title' value='Hello World' />" + -      "<textarea name='post[abc][body]' id='post_abc_body'>\nBack to the hill and over it again!</textarea>" + -      "<input name='post[abc][secret]' type='hidden' value='0' />" + +      "<input name='post[abc][title]' type='text' id='post_abc_title' value='Hello World' />" \ +      "<textarea name='post[abc][body]' id='post_abc_body'>\nBack to the hill and over it again!</textarea>" \ +      "<input name='post[abc][secret]' type='hidden' value='0' />" \        "<input name='post[abc][secret]' checked='checked' type='checkbox' id='post_abc_secret' value='1' />"      assert_dom_equal expected, output_buffer @@ -3069,9 +3071,9 @@ class FormHelperTest < ActionView::TestCase      end      expected = -      "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + -      "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" + -      "<input name='post[secret]' type='hidden' value='0' />" + +      "<input name='post[title]' type='text' id='post_title' value='Hello World' />" \ +      "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" \ +      "<input name='post[secret]' type='hidden' value='0' />" \        "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />"      assert_dom_equal expected, output_buffer @@ -3085,9 +3087,9 @@ class FormHelperTest < ActionView::TestCase      end      expected = -      "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + -      "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" + -      "<input name='post[secret]' type='hidden' value='0' />" + +      "<input name='post[title]' type='text' id='post_title' value='Hello World' />" \ +      "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" \ +      "<input name='post[secret]' type='hidden' value='0' />" \        "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />"      assert_dom_equal expected, output_buffer @@ -3099,7 +3101,7 @@ class FormHelperTest < ActionView::TestCase        concat f.text_field(:title)      end -    assert_dom_equal "<label for=\"author_post_title\">Title</label>" + +    assert_dom_equal "<label for=\"author_post_title\">Title</label>" \      "<input name='author[post][title]' type='text' id='author_post_title' value='Hello World' />",        output_buffer    end @@ -3110,7 +3112,7 @@ class FormHelperTest < ActionView::TestCase        concat f.text_field(:title)      end -    assert_dom_equal "<label for=\"author_post_1_title\">Title</label>" + +    assert_dom_equal "<label for=\"author_post_1_title\">Title</label>" \        "<input name='author[post][1][title]' type='text' id='author_post_1_title' value='Hello World' />",        output_buffer    end @@ -3130,9 +3132,9 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts/123", "create-post", "edit_post", method: "patch") do -      "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + -      "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" + -      "<input name='parent_post[secret]' type='hidden' value='0' />" + +      "<input name='post[title]' type='text' id='post_title' value='Hello World' />" \ +      "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" \ +      "<input name='parent_post[secret]' type='hidden' value='0' />" \        "<input name='parent_post[secret]' checked='checked' type='checkbox' id='parent_post_secret' value='1' />"      end @@ -3150,8 +3152,8 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts/123", "create-post", "edit_post", method: "patch") do -      "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + -      "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" + +      "<input name='post[title]' type='text' id='post_title' value='Hello World' />" \ +      "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" \        "<input name='post[comment][name]' type='text' id='post_comment_name' value='new comment' />"      end @@ -3190,8 +3192,8 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do -      "<label for='title'>Title:</label> <input name='post[title]' type='text' id='post_title' value='Hello World' /><br/>" + -      "<label for='body'>Body:</label> <textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea><br/>" + +      "<label for='title'>Title:</label> <input name='post[title]' type='text' id='post_title' value='Hello World' /><br/>" \ +      "<label for='body'>Body:</label> <textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea><br/>" \        "<label for='secret'>Secret:</label> <input name='post[secret]' type='hidden' value='0' /><input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' /><br/>"      end @@ -3209,8 +3211,8 @@ class FormHelperTest < ActionView::TestCase      end      expected = whole_form("/posts/123", "edit_post_123", "edit_post", method: "patch") do -      "<label for='title'>Title:</label> <input name='post[title]' type='text' id='post_title' value='Hello World' /><br/>" + -      "<label for='body'>Body:</label> <textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea><br/>" + +      "<label for='title'>Title:</label> <input name='post[title]' type='text' id='post_title' value='Hello World' /><br/>" \ +      "<label for='body'>Body:</label> <textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea><br/>" \        "<label for='secret'>Secret:</label> <input name='post[secret]' type='hidden' value='0' /><input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' /><br/>"      end @@ -3268,8 +3270,8 @@ class FormHelperTest < ActionView::TestCase      end      expected = -      "<label for='title'>Title:</label> <input name='post[title]' type='text' id='post_title' value='Hello World' /><br/>" + -      "<label for='body'>Body:</label> <textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea><br/>" + +      "<label for='title'>Title:</label> <input name='post[title]' type='text' id='post_title' value='Hello World' /><br/>" \ +      "<label for='body'>Body:</label> <textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea><br/>" \        "<label for='secret'>Secret:</label> <input name='post[secret]' type='hidden' value='0' /><input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' /><br/>"      assert_dom_equal expected, output_buffer diff --git a/actionview/test/template/form_options_helper_test.rb b/actionview/test/template/form_options_helper_test.rb index 51f5d25958..258dcdb806 100644 --- a/actionview/test/template/form_options_helper_test.rb +++ b/actionview/test/template/form_options_helper_test.rb @@ -393,30 +393,30 @@ class FormOptionsHelperTest < ActionView::TestCase    def test_time_zone_options_no_params      opts = time_zone_options_for_select -    assert_dom_equal "<option value=\"A\">A</option>\n" + -                 "<option value=\"B\">B</option>\n" + -                 "<option value=\"C\">C</option>\n" + -                 "<option value=\"D\">D</option>\n" + +    assert_dom_equal "<option value=\"A\">A</option>\n" \ +                 "<option value=\"B\">B</option>\n" \ +                 "<option value=\"C\">C</option>\n" \ +                 "<option value=\"D\">D</option>\n" \                   "<option value=\"E\">E</option>",                   opts    end    def test_time_zone_options_with_selected      opts = time_zone_options_for_select("D") -    assert_dom_equal "<option value=\"A\">A</option>\n" + -                 "<option value=\"B\">B</option>\n" + -                 "<option value=\"C\">C</option>\n" + -                 "<option value=\"D\" selected=\"selected\">D</option>\n" + +    assert_dom_equal "<option value=\"A\">A</option>\n" \ +                 "<option value=\"B\">B</option>\n" \ +                 "<option value=\"C\">C</option>\n" \ +                 "<option value=\"D\" selected=\"selected\">D</option>\n" \                   "<option value=\"E\">E</option>",                   opts    end    def test_time_zone_options_with_unknown_selected      opts = time_zone_options_for_select("K") -    assert_dom_equal "<option value=\"A\">A</option>\n" + -                 "<option value=\"B\">B</option>\n" + -                 "<option value=\"C\">C</option>\n" + -                 "<option value=\"D\">D</option>\n" + +    assert_dom_equal "<option value=\"A\">A</option>\n" \ +                 "<option value=\"B\">B</option>\n" \ +                 "<option value=\"C\">C</option>\n" \ +                 "<option value=\"D\">D</option>\n" \                   "<option value=\"E\">E</option>",                   opts    end @@ -424,11 +424,11 @@ class FormOptionsHelperTest < ActionView::TestCase    def test_time_zone_options_with_priority_zones      zones = [ ActiveSupport::TimeZone.new("B"), ActiveSupport::TimeZone.new("E") ]      opts = time_zone_options_for_select(nil, zones) -    assert_dom_equal "<option value=\"B\">B</option>\n" + -                 "<option value=\"E\">E</option>" + -                 "<option value=\"\" disabled=\"disabled\">-------------</option>\n" + -                 "<option value=\"A\">A</option>\n" + -                 "<option value=\"C\">C</option>\n" + +    assert_dom_equal "<option value=\"B\">B</option>\n" \ +                 "<option value=\"E\">E</option>" \ +                 "<option value=\"\" disabled=\"disabled\">-------------</option>\n" \ +                 "<option value=\"A\">A</option>\n" \ +                 "<option value=\"C\">C</option>\n" \                   "<option value=\"D\">D</option>",                   opts    end @@ -436,11 +436,11 @@ class FormOptionsHelperTest < ActionView::TestCase    def test_time_zone_options_with_selected_priority_zones      zones = [ ActiveSupport::TimeZone.new("B"), ActiveSupport::TimeZone.new("E") ]      opts = time_zone_options_for_select("E", zones) -    assert_dom_equal "<option value=\"B\">B</option>\n" + -                 "<option value=\"E\" selected=\"selected\">E</option>" + -                 "<option value=\"\" disabled=\"disabled\">-------------</option>\n" + -                 "<option value=\"A\">A</option>\n" + -                 "<option value=\"C\">C</option>\n" + +    assert_dom_equal "<option value=\"B\">B</option>\n" \ +                 "<option value=\"E\" selected=\"selected\">E</option>" \ +                 "<option value=\"\" disabled=\"disabled\">-------------</option>\n" \ +                 "<option value=\"A\">A</option>\n" \ +                 "<option value=\"C\">C</option>\n" \                   "<option value=\"D\">D</option>",                   opts    end @@ -448,11 +448,11 @@ class FormOptionsHelperTest < ActionView::TestCase    def test_time_zone_options_with_unselected_priority_zones      zones = [ ActiveSupport::TimeZone.new("B"), ActiveSupport::TimeZone.new("E") ]      opts = time_zone_options_for_select("C", zones) -    assert_dom_equal "<option value=\"B\">B</option>\n" + -                 "<option value=\"E\">E</option>" + -                 "<option value=\"\" disabled=\"disabled\">-------------</option>\n" + -                 "<option value=\"A\">A</option>\n" + -                 "<option value=\"C\" selected=\"selected\">C</option>\n" + +    assert_dom_equal "<option value=\"B\">B</option>\n" \ +                 "<option value=\"E\">E</option>" \ +                 "<option value=\"\" disabled=\"disabled\">-------------</option>\n" \ +                 "<option value=\"A\">A</option>\n" \ +                 "<option value=\"C\" selected=\"selected\">C</option>\n" \                   "<option value=\"D\">D</option>",                   opts    end @@ -1031,12 +1031,12 @@ class FormOptionsHelperTest < ActionView::TestCase    def test_time_zone_select      @firm = Firm.new("D")      html = time_zone_select("firm", "time_zone") -    assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" + -                 "<option value=\"A\">A</option>\n" + -                 "<option value=\"B\">B</option>\n" + -                 "<option value=\"C\">C</option>\n" + -                 "<option value=\"D\" selected=\"selected\">D</option>\n" + -                 "<option value=\"E\">E</option>" + +    assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" \ +                 "<option value=\"A\">A</option>\n" \ +                 "<option value=\"B\">B</option>\n" \ +                 "<option value=\"C\">C</option>\n" \ +                 "<option value=\"D\" selected=\"selected\">D</option>\n" \ +                 "<option value=\"E\">E</option>" \                   "</select>",                   html    end @@ -1049,12 +1049,12 @@ class FormOptionsHelperTest < ActionView::TestCase      end      assert_dom_equal( -      "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" + -      "<option value=\"A\">A</option>\n" + -      "<option value=\"B\">B</option>\n" + -      "<option value=\"C\">C</option>\n" + -      "<option value=\"D\" selected=\"selected\">D</option>\n" + -      "<option value=\"E\">E</option>" + +      "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" \ +      "<option value=\"A\">A</option>\n" \ +      "<option value=\"B\">B</option>\n" \ +      "<option value=\"C\">C</option>\n" \ +      "<option value=\"D\" selected=\"selected\">D</option>\n" \ +      "<option value=\"E\">E</option>" \        "</select>",        output_buffer      ) @@ -1068,12 +1068,12 @@ class FormOptionsHelperTest < ActionView::TestCase      end      assert_dom_equal( -      "<select id=\"firm_305_time_zone\" name=\"firm[305][time_zone]\">" + -      "<option value=\"A\">A</option>\n" + -      "<option value=\"B\">B</option>\n" + -      "<option value=\"C\">C</option>\n" + -      "<option value=\"D\" selected=\"selected\">D</option>\n" + -      "<option value=\"E\">E</option>" + +      "<select id=\"firm_305_time_zone\" name=\"firm[305][time_zone]\">" \ +      "<option value=\"A\">A</option>\n" \ +      "<option value=\"B\">B</option>\n" \ +      "<option value=\"C\">C</option>\n" \ +      "<option value=\"D\" selected=\"selected\">D</option>\n" \ +      "<option value=\"E\">E</option>" \        "</select>",        output_buffer      ) @@ -1088,12 +1088,12 @@ class FormOptionsHelperTest < ActionView::TestCase      end      assert_dom_equal( -      "<select id=\"firm_305_time_zone\" name=\"firm[305][time_zone]\">" + -      "<option value=\"A\">A</option>\n" + -      "<option value=\"B\">B</option>\n" + -      "<option value=\"C\">C</option>\n" + -      "<option value=\"D\" selected=\"selected\">D</option>\n" + -      "<option value=\"E\">E</option>" + +      "<select id=\"firm_305_time_zone\" name=\"firm[305][time_zone]\">" \ +      "<option value=\"A\">A</option>\n" \ +      "<option value=\"B\">B</option>\n" \ +      "<option value=\"C\">C</option>\n" \ +      "<option value=\"D\" selected=\"selected\">D</option>\n" \ +      "<option value=\"E\">E</option>" \        "</select>",        output_buffer      ) @@ -1102,13 +1102,13 @@ class FormOptionsHelperTest < ActionView::TestCase    def test_time_zone_select_with_blank      @firm = Firm.new("D")      html = time_zone_select("firm", "time_zone", nil, include_blank: true) -    assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" + -                 "<option value=\"\"></option>\n" + -                 "<option value=\"A\">A</option>\n" + -                 "<option value=\"B\">B</option>\n" + -                 "<option value=\"C\">C</option>\n" + -                 "<option value=\"D\" selected=\"selected\">D</option>\n" + -                 "<option value=\"E\">E</option>" + +    assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" \ +                 "<option value=\"\"></option>\n" \ +                 "<option value=\"A\">A</option>\n" \ +                 "<option value=\"B\">B</option>\n" \ +                 "<option value=\"C\">C</option>\n" \ +                 "<option value=\"D\" selected=\"selected\">D</option>\n" \ +                 "<option value=\"E\">E</option>" \                   "</select>",                   html    end @@ -1116,13 +1116,13 @@ class FormOptionsHelperTest < ActionView::TestCase    def test_time_zone_select_with_blank_as_string      @firm = Firm.new("D")      html = time_zone_select("firm", "time_zone", nil, include_blank: "No Zone") -    assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" + -                 "<option value=\"\">No Zone</option>\n" + -                 "<option value=\"A\">A</option>\n" + -                 "<option value=\"B\">B</option>\n" + -                 "<option value=\"C\">C</option>\n" + -                 "<option value=\"D\" selected=\"selected\">D</option>\n" + -                 "<option value=\"E\">E</option>" + +    assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" \ +                 "<option value=\"\">No Zone</option>\n" \ +                 "<option value=\"A\">A</option>\n" \ +                 "<option value=\"B\">B</option>\n" \ +                 "<option value=\"C\">C</option>\n" \ +                 "<option value=\"D\" selected=\"selected\">D</option>\n" \ +                 "<option value=\"E\">E</option>" \                   "</select>",                   html    end @@ -1131,12 +1131,12 @@ class FormOptionsHelperTest < ActionView::TestCase      @firm = Firm.new("D")      html = time_zone_select("firm", "time_zone", nil, {},        "style" => "color: red") -    assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\" style=\"color: red\">" + -                 "<option value=\"A\">A</option>\n" + -                 "<option value=\"B\">B</option>\n" + -                 "<option value=\"C\">C</option>\n" + -                 "<option value=\"D\" selected=\"selected\">D</option>\n" + -                 "<option value=\"E\">E</option>" + +    assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\" style=\"color: red\">" \ +                 "<option value=\"A\">A</option>\n" \ +                 "<option value=\"B\">B</option>\n" \ +                 "<option value=\"C\">C</option>\n" \ +                 "<option value=\"D\" selected=\"selected\">D</option>\n" \ +                 "<option value=\"E\">E</option>" \                   "</select>",                   html      assert_dom_equal html, time_zone_select("firm", "time_zone", nil, {}, @@ -1147,13 +1147,13 @@ class FormOptionsHelperTest < ActionView::TestCase      @firm = Firm.new("D")      html = time_zone_select("firm", "time_zone", nil,        { include_blank: true }, "style" => "color: red") -    assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\" style=\"color: red\">" + -                 "<option value=\"\"></option>\n" + -                 "<option value=\"A\">A</option>\n" + -                 "<option value=\"B\">B</option>\n" + -                 "<option value=\"C\">C</option>\n" + -                 "<option value=\"D\" selected=\"selected\">D</option>\n" + -                 "<option value=\"E\">E</option>" + +    assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\" style=\"color: red\">" \ +                 "<option value=\"\"></option>\n" \ +                 "<option value=\"A\">A</option>\n" \ +                 "<option value=\"B\">B</option>\n" \ +                 "<option value=\"C\">C</option>\n" \ +                 "<option value=\"D\" selected=\"selected\">D</option>\n" \ +                 "<option value=\"E\">E</option>" \                   "</select>",                   html      assert_dom_equal html, time_zone_select("firm", "time_zone", nil, @@ -1164,13 +1164,13 @@ class FormOptionsHelperTest < ActionView::TestCase      @firm = Firm.new("D")      html = time_zone_select("firm", "time_zone", nil,        { include_blank: "No Zone" }, "style" => "color: red") -    assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\" style=\"color: red\">" + -                 "<option value=\"\">No Zone</option>\n" + -                 "<option value=\"A\">A</option>\n" + -                 "<option value=\"B\">B</option>\n" + -                 "<option value=\"C\">C</option>\n" + -                 "<option value=\"D\" selected=\"selected\">D</option>\n" + -                 "<option value=\"E\">E</option>" + +    assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\" style=\"color: red\">" \ +                 "<option value=\"\">No Zone</option>\n" \ +                 "<option value=\"A\">A</option>\n" \ +                 "<option value=\"B\">B</option>\n" \ +                 "<option value=\"C\">C</option>\n" \ +                 "<option value=\"D\" selected=\"selected\">D</option>\n" \ +                 "<option value=\"E\">E</option>" \                   "</select>",                   html      assert_dom_equal html, time_zone_select("firm", "time_zone", nil, @@ -1181,13 +1181,13 @@ class FormOptionsHelperTest < ActionView::TestCase      @firm = Firm.new("D")      zones = [ ActiveSupport::TimeZone.new("A"), ActiveSupport::TimeZone.new("D") ]      html = time_zone_select("firm", "time_zone", zones) -    assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" + -                 "<option value=\"A\">A</option>\n" + -                 "<option value=\"D\" selected=\"selected\">D</option>" + -                 "<option value=\"\" disabled=\"disabled\">-------------</option>\n" + -                 "<option value=\"B\">B</option>\n" + -                 "<option value=\"C\">C</option>\n" + -                 "<option value=\"E\">E</option>" + +    assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" \ +                 "<option value=\"A\">A</option>\n" \ +                 "<option value=\"D\" selected=\"selected\">D</option>" \ +                 "<option value=\"\" disabled=\"disabled\">-------------</option>\n" \ +                 "<option value=\"B\">B</option>\n" \ +                 "<option value=\"C\">C</option>\n" \ +                 "<option value=\"E\">E</option>" \                   "</select>",                   html    end @@ -1200,13 +1200,13 @@ class FormOptionsHelperTest < ActionView::TestCase      end      html = time_zone_select("firm", "time_zone", /A|D/) -    assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" + -                 "<option value=\"A\">A</option>\n" + -                 "<option value=\"D\" selected=\"selected\">D</option>" + -                 "<option value=\"\" disabled=\"disabled\">-------------</option>\n" + -                 "<option value=\"B\">B</option>\n" + -                 "<option value=\"C\">C</option>\n" + -                 "<option value=\"E\">E</option>" + +    assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" \ +                 "<option value=\"A\">A</option>\n" \ +                 "<option value=\"D\" selected=\"selected\">D</option>" \ +                 "<option value=\"\" disabled=\"disabled\">-------------</option>\n" \ +                 "<option value=\"B\">B</option>\n" \ +                 "<option value=\"C\">C</option>\n" \ +                 "<option value=\"E\">E</option>" \                   "</select>",                   html    end @@ -1221,13 +1221,13 @@ class FormOptionsHelperTest < ActionView::TestCase      end      html = time_zone_select("firm", "time_zone", /A|D/) -    assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" + -                 "<option value=\"\" disabled=\"disabled\">-------------</option>\n" + -                 "<option value=\"A\">A</option>\n" + -                 "<option value=\"B\">B</option>\n" + -                 "<option value=\"C\">C</option>\n" + -                 "<option value=\"D\" selected=\"selected\">D</option>\n" + -                 "<option value=\"E\">E</option>" + +    assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" \ +                 "<option value=\"\" disabled=\"disabled\">-------------</option>\n" \ +                 "<option value=\"A\">A</option>\n" \ +                 "<option value=\"B\">B</option>\n" \ +                 "<option value=\"C\">C</option>\n" \ +                 "<option value=\"D\" selected=\"selected\">D</option>\n" \ +                 "<option value=\"E\">E</option>" \                   "</select>",                   html    end @@ -1237,12 +1237,12 @@ class FormOptionsHelperTest < ActionView::TestCase      @firm.time_zone = nil      html = time_zone_select("firm", "time_zone", nil, default: "B") -    assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" + -                  "<option value=\"A\">A</option>\n" + -                  "<option value=\"B\" selected=\"selected\">B</option>\n" + -                  "<option value=\"C\">C</option>\n" + -                  "<option value=\"D\">D</option>\n" + -                  "<option value=\"E\">E</option>" + +    assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" \ +                  "<option value=\"A\">A</option>\n" \ +                  "<option value=\"B\" selected=\"selected\">B</option>\n" \ +                  "<option value=\"C\">C</option>\n" \ +                  "<option value=\"D\">D</option>\n" \ +                  "<option value=\"E\">E</option>" \                    "</select>",                    html    end @@ -1251,12 +1251,12 @@ class FormOptionsHelperTest < ActionView::TestCase      @firm = Firm.new("D")      html = time_zone_select("firm", "time_zone", nil, default: "B") -    assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" + -                  "<option value=\"A\">A</option>\n" + -                  "<option value=\"B\">B</option>\n" + -                  "<option value=\"C\">C</option>\n" + -                  "<option value=\"D\" selected=\"selected\">D</option>\n" + -                  "<option value=\"E\">E</option>" + +    assert_dom_equal "<select id=\"firm_time_zone\" name=\"firm[time_zone]\">" \ +                  "<option value=\"A\">A</option>\n" \ +                  "<option value=\"B\">B</option>\n" \ +                  "<option value=\"C\">C</option>\n" \ +                  "<option value=\"D\" selected=\"selected\">D</option>\n" \ +                  "<option value=\"E\">E</option>" \                    "</select>",                    html    end diff --git a/actionview/test/template/form_tag_helper_test.rb b/actionview/test/template/form_tag_helper_test.rb index 24ae6b8b90..084c540139 100644 --- a/actionview/test/template/form_tag_helper_test.rb +++ b/actionview/test/template/form_tag_helper_test.rb @@ -510,6 +510,13 @@ class FormTagHelperTest < ActionView::TestCase      )    end +  def test_submit_tag_doesnt_have_data_disable_with_twice_with_hash +    assert_equal( +      %(<input type="submit" name="commit" value="Save" data-disable-with="Processing..." />), +      submit_tag("Save", data: { disable_with: "Processing..." }) +    ) +  end +    def test_submit_tag_with_symbol_value      assert_dom_equal(        %(<input data-disable-with="Save" name='commit' type="submit" value="Save" />), @@ -694,31 +701,31 @@ class FormTagHelperTest < ActionView::TestCase    def test_text_area_tag_options_symbolize_keys_side_effects      options = { option: "random_option" }      text_area_tag "body", "hello world", options -    assert_equal options, option: "random_option" +    assert_equal({ option: "random_option" }, options)    end    def test_submit_tag_options_symbolize_keys_side_effects      options = { option: "random_option" }      submit_tag "submit value", options -    assert_equal options, option: "random_option" +    assert_equal({ option: "random_option" }, options)    end    def test_button_tag_options_symbolize_keys_side_effects      options = { option: "random_option" }      button_tag "button value", options -    assert_equal options, option: "random_option" +    assert_equal({ option: "random_option" }, options)    end    def test_image_submit_tag_options_symbolize_keys_side_effects      options = { option: "random_option" }      image_submit_tag "submit source", options -    assert_equal options, option: "random_option" +    assert_equal({ option: "random_option" }, options)    end    def test_image_label_tag_options_symbolize_keys_side_effects      options = { option: "random_option" }      label_tag "submit source", "title", options -    assert_equal options, option: "random_option" +    assert_equal({ option: "random_option" }, options)    end    def protect_against_forgery? diff --git a/actionview/test/template/output_safety_helper_test.rb b/actionview/test/template/output_safety_helper_test.rb index 8691bb11ee..537b4393ee 100644 --- a/actionview/test/template/output_safety_helper_test.rb +++ b/actionview/test/template/output_safety_helper_test.rb @@ -33,6 +33,22 @@ class OutputSafetyHelperTest < ActionView::TestCase      assert_equal ""a" <br/> <b> <br/> <c>", joined    end +  test "safe_join should return the safe string separated by $, when second argument is not passed" do +    default_delimeter = $, + +    begin +      $, = nil +      joined = safe_join(["a", "b"]) +      assert_equal "ab", joined + +      $, = "|" +      joined = safe_join(["a", "b"]) +      assert_equal "a|b", joined +    ensure +      $, = default_delimeter +    end +  end +    test "to_sentence should escape non-html_safe values" do      actual = to_sentence(%w(< > & ' "))      assert actual.html_safe? @@ -87,4 +103,15 @@ class OutputSafetyHelperTest < ActionView::TestCase      assert_equal "one, two three", to_sentence(["one", "two", "three"], last_word_connector: " ")      assert_equal "one, two and three", to_sentence(["one", "two", "three"], last_word_connector: " and ")    end + +  test "to_sentence is not affected by $," do +    separator_was = $, +    $, = "|" +    begin +      assert_equal "one and two", to_sentence(["one", "two"]) +      assert_equal "one, two, and three", to_sentence(["one", "two", "three"]) +    ensure +      $, = separator_was +    end +  end  end diff --git a/actionview/test/template/render_test.rb b/actionview/test/template/render_test.rb index e7e0b147c7..fbe0e555bd 100644 --- a/actionview/test/template/render_test.rb +++ b/actionview/test/template/render_test.rb @@ -220,15 +220,15 @@ module RenderTestCases    def test_render_partial_with_invalid_option_as      e = assert_raises(ArgumentError) { @view.render(partial: "test/partial_only", as: "a-in") } -    assert_equal "The value (a-in) of the option `as` is not a valid Ruby identifier; " + -      "make sure it starts with lowercase letter, " + +    assert_equal "The value (a-in) of the option `as` is not a valid Ruby identifier; " \ +      "make sure it starts with lowercase letter, " \        "and is followed by any combination of letters, numbers and underscores.", e.message    end    def test_render_partial_with_hyphen_and_invalid_option_as      e = assert_raises(ArgumentError) { @view.render(partial: "test/a-in", as: "a-in") } -    assert_equal "The value (a-in) of the option `as` is not a valid Ruby identifier; " + -      "make sure it starts with lowercase letter, " + +    assert_equal "The value (a-in) of the option `as` is not a valid Ruby identifier; " \ +      "make sure it starts with lowercase letter, " \        "and is followed by any combination of letters, numbers and underscores.", e.message    end @@ -253,7 +253,7 @@ module RenderTestCases    def test_render_sub_template_with_errors      e = assert_raises(ActionView::Template::Error) { @view.render(template: "test/sub_template_raise") }      assert_match %r!method.*doesnt_exist!, e.message -    assert_equal "Trace of template inclusion: #{File.expand_path("#{FIXTURE_LOAD_PATH}/test/sub_template_raise.html.erb")}", e.sub_template_message +    assert_match %r{Trace of template inclusion: .*test/sub_template_raise.html.erb}, e.sub_template_message      assert_equal "1", e.line_number      assert_equal File.expand_path("#{FIXTURE_LOAD_PATH}/test/_raise.html.erb"), e.file_name    end @@ -301,6 +301,15 @@ module RenderTestCases        @view.render(partial: "test/local_inspector", collection: [ Customer.new("mary") ])    end +  def test_render_partial_collection_with_different_partials_still_provides_partial_iteration +    a = {} +    b = {} +    def a.to_partial_path; "test/partial_iteration_1"; end +    def b.to_partial_path; "test/partial_iteration_2"; end + +    assert_equal "local-variable\nlocal-variable", @controller_view.render([a, b]) +  end +    def test_render_partial_with_empty_collection_should_return_nil      assert_nil @view.render(partial: "test/customer", collection: [])    end @@ -393,8 +402,7 @@ module RenderTestCases      assert_equal :partial_name_local_variable, exception.cause.name    end -  # TODO: The reason for this test is unclear, improve documentation -  def test_render_partial_and_fallback_to_layout +  def test_render_partial_with_no_block_given_to_yield      assert_equal "Before (Josh)\n\nAfter", @view.render(partial: "test/layout_for_partial", locals: { name: "Josh" })    end @@ -424,7 +432,7 @@ module RenderTestCases    end    CustomHandler = lambda do |template| -    "@output_buffer = ''\n" + +    "@output_buffer = ''\n" \        "@output_buffer << 'source: #{template.source.inspect}'\n"    end @@ -475,11 +483,9 @@ module RenderTestCases    end    def test_render_ignores_templates_with_malformed_template_handlers -    ActiveSupport::Deprecation.silence do -      %w(malformed malformed.erb malformed.html.erb malformed.en.html.erb).each do |name| -        assert File.exist?(File.expand_path("#{FIXTURE_LOAD_PATH}/test/malformed/#{name}~")), "Malformed file (#{name}~) which should be ignored does not exists" -        assert_raises(ActionView::MissingTemplate) { @view.render(file: "test/malformed/#{name}") } -      end +    %w(malformed malformed.erb malformed.html.erb malformed.en.html.erb).each do |name| +      assert File.exist?(File.expand_path("#{FIXTURE_LOAD_PATH}/test/malformed/#{name}~")), "Malformed file (#{name}~) which should be ignored does not exists" +      assert_raises(ActionView::MissingTemplate) { @view.render(file: "test/malformed/#{name}") }      end    end diff --git a/actionview/test/template/text_test.rb b/actionview/test/template/text_test.rb index ee526dc367..72ffd5f3be 100644 --- a/actionview/test/template/text_test.rb +++ b/actionview/test/template/text_test.rb @@ -4,4 +4,20 @@ class TextTest < ActiveSupport::TestCase    test "formats always return :text" do      assert_equal [:text], ActionView::Template::Text.new("").formats    end + +  test "identifier should return 'text template'" do +    assert_equal "text template", ActionView::Template::Text.new("").identifier +  end + +  test "inspect should return 'text template'" do +    assert_equal "text template", ActionView::Template::Text.new("").inspect +  end + +  test "to_str should return a given string" do +    assert_equal "a cat", ActionView::Template::Text.new("a cat").to_str +  end + +  test "render should return a given string" do +    assert_equal "a dog", ActionView::Template::Text.new("a dog").render +  end  end diff --git a/actionview/test/template/url_helper_test.rb b/actionview/test/template/url_helper_test.rb index 1e64385b52..09454b32cc 100644 --- a/actionview/test/template/url_helper_test.rb +++ b/actionview/test/template/url_helper_test.rb @@ -235,14 +235,14 @@ class UrlHelperTest < ActiveSupport::TestCase      end    end -  def test_button_to_with_permited_strong_params +  def test_button_to_with_permitted_strong_params      assert_dom_equal(        %{<form action="http://www.example.com" class="button_to" method="post"><input type="submit" value="Hello" /><input type="hidden" name="baz" value="quux" /><input type="hidden" name="foo" value="bar" /></form>},        button_to("Hello", "http://www.example.com", params: FakeParams.new)      )    end -  def test_button_to_with_unpermited_strong_params +  def test_button_to_with_unpermitted_strong_params      assert_raises(ArgumentError) do        button_to("Hello", "http://www.example.com", params: FakeParams.new(false))      end @@ -496,6 +496,15 @@ class UrlHelperTest < ActiveSupport::TestCase      assert current_page?("http://www.example.com/")    end +  def test_current_page_considering_params +    @request = request_for_url("/?order=desc&page=1") + +    assert !current_page?(url_hash, check_parameters: true) +    assert !current_page?(url_hash.merge(check_parameters: true)) +    assert !current_page?(ActionController::Parameters.new(url_hash.merge(check_parameters: true)).permit!) +    assert !current_page?("http://www.example.com/", check_parameters: true) +  end +    def test_current_page_with_params_that_match      @request = request_for_url("/?order=desc&page=1") @@ -812,7 +821,7 @@ class TasksController < ActionController::Base    private      def render_default -      render inline: "<%= link_to_unless_current('tasks', tasks_path) %>\n" + +      render inline: "<%= link_to_unless_current('tasks', tasks_path) %>\n" \          "<%= link_to_unless_current('tasks', tasks_url) %>"      end  end diff --git a/actionview/test/ujs/config.ru b/actionview/test/ujs/config.ru index cb961dc140..414c2063c3 100644 --- a/actionview/test/ujs/config.ru +++ b/actionview/test/ujs/config.ru @@ -1,3 +1,3 @@ -$LOAD_PATH.unshift File.expand_path('..', __FILE__) -require 'server' +$LOAD_PATH.unshift File.expand_path("..", __FILE__) +require "server"  run UJS::Server diff --git a/actionview/test/ujs/public/test/settings.js b/actionview/test/ujs/public/test/settings.js index 3375456f11..c68ca24d6a 100644 --- a/actionview/test/ujs/public/test/settings.js +++ b/actionview/test/ujs/public/test/settings.js @@ -76,7 +76,7 @@ try {  }  $.fn.extend({ -  // trigger an native click event +  // trigger a native click event    triggerNative: function(type, options) {      var el = this[0],          event, diff --git a/actionview/test/ujs/public/vendor/jquery.metadata.js b/actionview/test/ujs/public/vendor/jquery.metadata.js index ad8bfba404..5b5253cdf7 100644 --- a/actionview/test/ujs/public/vendor/jquery.metadata.js +++ b/actionview/test/ujs/public/vendor/jquery.metadata.js @@ -111,7 +111,7 @@ $.extend({   *   * @name metadata   * @descr Returns element's metadata object - * @param Object opts An object contianing settings to override the defaults + * @param Object opts An object containing settings to override the defaults   * @type jQuery   * @cat Plugins/Metadata   */ diff --git a/actionview/test/ujs/server.rb b/actionview/test/ujs/server.rb index cc02cd8419..25f70baf5f 100644 --- a/actionview/test/ujs/server.rb +++ b/actionview/test/ujs/server.rb @@ -12,7 +12,7 @@ module UJS        get "/rails-ujs.js" => Blade::Assets.environment        get "/" => "tests#index"        match "/echo" => "tests#echo", via: :all -      get "/error" => proc {|env| [403, {}, []] } +      get "/error" => proc { |env| [403, {}, []] }      end      config.cache_classes = false @@ -26,7 +26,7 @@ module UJS  end  module TestsHelper -  def jquery_link version +  def jquery_link(version)      if params[:version] == version        "[#{version}]"      else @@ -34,7 +34,7 @@ module TestsHelper      end    end -  def cdn_link cdn +  def cdn_link(cdn)      if params[:cdn] == cdn        "[#{cdn}]"      else @@ -43,22 +43,22 @@ module TestsHelper    end    def jquery_src -    if params[:version] == 'edge' +    if params[:version] == "edge"        "/vendor/jquery.js" -    elsif params[:cdn] && params[:cdn] == 'googleapis' +    elsif params[:cdn] && params[:cdn] == "googleapis"        "https://ajax.googleapis.com/ajax/libs/jquery/#{params[:version]}/jquery.min.js"      else        "http://code.jquery.com/jquery-#{params[:version]}.js"      end    end -  def test_to *names +  def test_to(*names)      names = ["/vendor/qunit.js", "settings"] + names      names.map { |name| script_tag name }.join("\n").html_safe    end -  def script_tag src -    src = "/test/#{src}.js" unless src.index('/') +  def script_tag(src) +    src = "/test/#{src}.js" unless src.index("/")      %(<script src="#{src}" type="text/javascript"></script>).html_safe    end @@ -72,20 +72,20 @@ class TestsController < ActionController::Base    layout "application"    def index -    params[:version] ||= ENV['JQUERY_VERSION'] || '1.11.0' -    params[:cdn] ||= 'jquery' +    params[:version] ||= ENV["JQUERY_VERSION"] || "1.11.0" +    params[:cdn] ||= "jquery"      render :index    end    def echo -    data = { :params => params.to_unsafe_h }.update(request.env) +    data = { params: params.to_unsafe_h }.update(request.env) -    if params[:content_type] and params[:content] +    if params[:content_type] && params[:content]        render inline: params[:content], content_type: params[:content_type]      elsif request.xhr?        render json: JSON.generate(data)      elsif params[:iframe] -      payload = JSON.generate(data).gsub('<', '<').gsub('>', '>') +      payload = JSON.generate(data).gsub("<", "<").gsub(">", ">")        html = <<-HTML          <script>            if (window.top && window.top !== window) diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md index 5e8d8cb5c9..3bc34c1598 100644 --- a/activejob/CHANGELOG.md +++ b/activejob/CHANGELOG.md @@ -1,3 +1,13 @@ +*   Correctly set test adapter when configure the queue adapter on a per job. + +    Fixes #26360. + +    *Yuji Yaginuma* + +*   Push skipped jobs to `enqueued_jobs` when using `perform_enqueued_jobs` with a `only` filter in tests + +    *Alexander Pauly* +    *   Removed deprecated support to passing the adapter class to `.queue_adapter`.      *Rafael Mendonça França* diff --git a/activejob/MIT-LICENSE b/activejob/MIT-LICENSE index a3ffb46b3f..daa726b9f0 100644 --- a/activejob/MIT-LICENSE +++ b/activejob/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2014-2016 David Heinemeier Hansson +Copyright (c) 2014-2017 David Heinemeier Hansson  Permission is hereby granted, free of charge, to any person obtaining  a copy of this software and associated documentation files (the diff --git a/activejob/Rakefile b/activejob/Rakefile index c074487fbe..41ff76135e 100644 --- a/activejob/Rakefile +++ b/activejob/Rakefile @@ -1,7 +1,8 @@  require "rake/testtask"  #TODO: add qu back to the list after it support Rails 5.1 -ACTIVEJOB_ADAPTERS = %w(async inline delayed_job que queue_classic resque sidekiq sneakers sucker_punch backburner test) +#TODO: add delayed_job back to the list after it support Rails 5.1 +ACTIVEJOB_ADAPTERS = %w(async inline que queue_classic resque sidekiq sneakers sucker_punch backburner test)  ACTIVEJOB_ADAPTERS.delete("queue_classic") if defined?(JRUBY_VERSION)  task default: :test diff --git a/activejob/bin/test b/activejob/bin/test new file mode 100755 index 0000000000..a7beb14b27 --- /dev/null +++ b/activejob/bin/test @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby + +COMPONENT_ROOT = File.expand_path("..", __dir__) +require File.expand_path("../tools/test", COMPONENT_ROOT) diff --git a/activejob/lib/active_job.rb b/activejob/lib/active_job.rb index 20ca7085c6..8b7aef65a2 100644 --- a/activejob/lib/active_job.rb +++ b/activejob/lib/active_job.rb @@ -1,5 +1,5 @@  #-- -# Copyright (c) 2014-2016 David Heinemeier Hansson +# Copyright (c) 2014-2017 David Heinemeier Hansson  #  # Permission is hereby granted, free of charge, to any person obtaining  # a copy of this software and associated documentation files (the diff --git a/activejob/lib/active_job/queue_adapter.rb b/activejob/lib/active_job/queue_adapter.rb index bcc555d33e..9dae80ffc2 100644 --- a/activejob/lib/active_job/queue_adapter.rb +++ b/activejob/lib/active_job/queue_adapter.rb @@ -1,4 +1,3 @@ -require "active_job/queue_adapters/inline_adapter"  require "active_support/core_ext/string/inflections"  module ActiveJob diff --git a/activejob/lib/active_job/queue_adapters/test_adapter.rb b/activejob/lib/active_job/queue_adapters/test_adapter.rb index da042cfebf..ec825f12cd 100644 --- a/activejob/lib/active_job/queue_adapters/test_adapter.rb +++ b/activejob/lib/active_job/queue_adapters/test_adapter.rb @@ -24,15 +24,11 @@ module ActiveJob        end        def enqueue(job) #:nodoc: -        return if filtered?(job) -          job_data = job_to_hash(job)          enqueue_or_perform(perform_enqueued_jobs, job, job_data)        end        def enqueue_at(job, timestamp) #:nodoc: -        return if filtered?(job) -          job_data = job_to_hash(job, at: timestamp)          enqueue_or_perform(perform_enqueued_at_jobs, job, job_data)        end @@ -44,11 +40,11 @@ module ActiveJob          end          def enqueue_or_perform(perform, job, job_data) -          if perform +          if !perform || filtered?(job) +            enqueued_jobs << job_data +          else              performed_jobs << job_data              Base.execute job.serialize -          else -            enqueued_jobs << job_data            end          end diff --git a/activejob/lib/active_job/test_helper.rb b/activejob/lib/active_job/test_helper.rb index d01795f0c5..a61e4f59a5 100644 --- a/activejob/lib/active_job/test_helper.rb +++ b/activejob/lib/active_job/test_helper.rb @@ -8,16 +8,35 @@ module ActiveJob        :performed_jobs, :performed_jobs=,        to: :queue_adapter +    module TestQueueAdapter +      extend ActiveSupport::Concern + +      included do +        class_attribute :_test_adapter, instance_accessor: false, instance_predicate: false +      end + +      module ClassMethods +        def queue_adapter +          self._test_adapter.nil? ? super : self._test_adapter +        end + +        def disable_test_adapter +          self._test_adapter = nil +        end + +        def enable_test_adapter(test_adapter) +          self._test_adapter = test_adapter +        end +      end +    end + +    ActiveJob::Base.include(TestQueueAdapter) +      def before_setup # :nodoc:        test_adapter = queue_adapter_for_test -      @old_queue_adapters = (ActiveJob::Base.descendants << ActiveJob::Base).select do |klass| -        # only override explicitly set adapters, a quirk of `class_attribute` -        klass.singleton_class.public_instance_methods(false).include?(:_queue_adapter) -      end.map do |klass| -        [klass, klass.queue_adapter].tap do -          klass.queue_adapter = test_adapter -        end +      queue_adapter_changed_jobs.each do |klass| +        klass.enable_test_adapter(test_adapter)        end        clear_enqueued_jobs @@ -27,9 +46,8 @@ module ActiveJob      def after_teardown # :nodoc:        super -      @old_queue_adapters.each do |(klass, adapter)| -        klass.queue_adapter = adapter -      end + +      queue_adapter_changed_jobs.each { |klass| klass.disable_test_adapter }      end      # Specifies the queue adapter to use with all active job test helpers. @@ -55,7 +73,7 @@ module ActiveJob      #     assert_enqueued_jobs 2      #   end      # -    # If a block is passed, that block should cause the specified number of +    # If a block is passed, that block will cause the specified number of      # jobs to be enqueued.      #      #   def test_jobs_again @@ -77,14 +95,23 @@ module ActiveJob      #       HelloJob.perform_later('jeremy')      #     end      #   end -    def assert_enqueued_jobs(number, only: nil) +    # +    # The number of times a job is enqueued to a specific queue can also be asserted. +    # +    #   def test_logging_job +    #     assert_enqueued_jobs 2, queue: 'default' do +    #       LoggingJob.perform_later +    #       HelloJob.perform_later('elfassy') +    #     end +    #   end +    def assert_enqueued_jobs(number, only: nil, queue: nil)        if block_given? -        original_count = enqueued_jobs_size(only: only) +        original_count = enqueued_jobs_size(only: only, queue: queue)          yield -        new_count = enqueued_jobs_size(only: only) +        new_count = enqueued_jobs_size(only: only, queue: queue)          assert_equal number, new_count - original_count, "#{number} jobs expected, but #{new_count - original_count} were enqueued"        else -        actual_count = enqueued_jobs_size(only: only) +        actual_count = enqueued_jobs_size(only: only, queue: queue)          assert_equal number, actual_count, "#{number} jobs expected, but #{actual_count} were enqueued"        end      end @@ -284,7 +311,7 @@ module ActiveJob      #   def test_perform_enqueued_jobs_with_only      #     perform_enqueued_jobs(only: MyJob) do      #       MyJob.perform_later(1, 2, 3) # will be performed -    #       HelloJob.perform_later(1, 2, 3) # will not be perfomed +    #       HelloJob.perform_later(1, 2, 3) # will not be performed      #     end      #     assert_performed_jobs 1      #   end @@ -323,11 +350,16 @@ module ActiveJob          performed_jobs.clear        end -      def enqueued_jobs_size(only: nil) -        if only -          enqueued_jobs.count { |job| Array(only).include?(job.fetch(:job)) } -        else -          enqueued_jobs.count +      def enqueued_jobs_size(only: nil, queue: nil) +        enqueued_jobs.count do |job| +          job_class = job.fetch(:job) +          if only +            next false unless Array(only).include?(job_class) +          end +          if queue +            next false unless queue.to_s == job.fetch(:queue, job_class.queue_name) +          end +          true          end        end @@ -344,5 +376,12 @@ module ActiveJob          job.queue_name = payload[:queue]          job        end + +      def queue_adapter_changed_jobs +        (ActiveJob::Base.descendants << ActiveJob::Base).select do |klass| +          # only override explicitly set adapters, a quirk of `class_attribute` +          klass.singleton_class.public_instance_methods(false).include?(:_queue_adapter) +        end +      end    end  end diff --git a/activejob/lib/rails/generators/job/job_generator.rb b/activejob/lib/rails/generators/job/job_generator.rb index 97c11a9ea6..50476a2e50 100644 --- a/activejob/lib/rails/generators/job/job_generator.rb +++ b/activejob/lib/rails/generators/job/job_generator.rb @@ -19,7 +19,7 @@ module Rails # :nodoc:          template "job.rb", File.join("app/jobs", class_path, "#{file_name}_job.rb")          in_root do -          if self.behavior == :invoke && !File.exist?(application_job_file_name) +          if behavior == :invoke && !File.exist?(application_job_file_name)              template "application_job.rb", application_job_file_name            end          end diff --git a/activejob/test/cases/queue_adapter_test.rb b/activejob/test/cases/queue_adapter_test.rb index f1e0cf78ad..9611b0909b 100644 --- a/activejob/test/cases/queue_adapter_test.rb +++ b/activejob/test/cases/queue_adapter_test.rb @@ -21,6 +21,7 @@ class QueueAdapterTest < ActiveJob::TestCase    end    test "should allow overriding the queue_adapter at the child class level without affecting the parent or its sibling" do +    ActiveJob::Base.disable_test_adapter      base_queue_adapter = ActiveJob::Base.queue_adapter      child_job_one = Class.new(ActiveJob::Base) diff --git a/activejob/test/cases/queue_naming_test.rb b/activejob/test/cases/queue_naming_test.rb index 0e1e0decd1..0247bc111e 100644 --- a/activejob/test/cases/queue_naming_test.rb +++ b/activejob/test/cases/queue_naming_test.rb @@ -56,7 +56,7 @@ class QueueNamingTest < ActiveSupport::TestCase      original_queue_name = HelloJob.queue_name      begin -      HelloJob.queue_as { self.arguments.first == "1" ? :one : :two } +      HelloJob.queue_as { arguments.first == "1" ? :one : :two }        assert_equal "one", HelloJob.new("1").queue_name        assert_equal "two", HelloJob.new("3").queue_name      ensure diff --git a/activejob/test/cases/queue_priority_test.rb b/activejob/test/cases/queue_priority_test.rb index b0d0e903fc..171fb1e593 100644 --- a/activejob/test/cases/queue_priority_test.rb +++ b/activejob/test/cases/queue_priority_test.rb @@ -32,7 +32,7 @@ class QueuePriorityTest < ActiveSupport::TestCase      original_priority = HelloJob.priority      begin -      HelloJob.queue_with_priority { self.arguments.first == "1" ? 99 : 11 } +      HelloJob.queue_with_priority { arguments.first == "1" ? 99 : 11 }        assert_equal 99, HelloJob.new("1").priority        assert_equal 11, HelloJob.new("3").priority      ensure diff --git a/activejob/test/cases/test_helper_test.rb b/activejob/test/cases/test_helper_test.rb index 51fc6cc0c4..2e6357f824 100644 --- a/activejob/test/cases/test_helper_test.rb +++ b/activejob/test/cases/test_helper_test.rb @@ -56,6 +56,17 @@ class EnqueuedJobsTest < ActiveJob::TestCase      end    end +  def test_assert_enqueued_jobs_when_performing_with_only_option +    assert_nothing_raised do +      assert_enqueued_jobs 1, only: HelloJob do +        perform_enqueued_jobs only: LoggingJob do +          HelloJob.perform_later("sean") +          LoggingJob.perform_later("yves") +        end +      end +    end +  end +    def test_assert_no_enqueued_jobs_with_no_block      assert_nothing_raised do        assert_no_enqueued_jobs @@ -110,6 +121,27 @@ class EnqueuedJobsTest < ActiveJob::TestCase      end    end +  def test_assert_enqueued_jobs_with_only_and_queue_option +    assert_nothing_raised do +      assert_enqueued_jobs 1, only: HelloJob, queue: :some_queue do +        HelloJob.set(queue: :some_queue).perform_later +        HelloJob.set(queue: :other_queue).perform_later +        LoggingJob.perform_later +      end +    end +  end + +  def test_assert_enqueued_jobs_with_queue_option +    assert_nothing_raised do +      assert_enqueued_jobs 2, queue: :default do +        HelloJob.perform_later +        LoggingJob.perform_later +        HelloJob.set(queue: :other_queue).perform_later +        LoggingJob.set(queue: :other_queue).perform_later +      end +    end +  end +    def test_assert_enqueued_jobs_with_only_option_and_none_sent      error = assert_raise ActiveSupport::TestCase::Assertion do        assert_enqueued_jobs 1, only: HelloJob do @@ -528,3 +560,20 @@ class InheritedJobTest < ActiveJob::TestCase      assert_instance_of ActiveJob::QueueAdapters::TestAdapter, InheritedJob.queue_adapter    end  end + +class QueueAdapterJobTest < ActiveJob::TestCase +  def before_setup +    @original_autoload_paths = ActiveSupport::Dependencies.autoload_paths +    ActiveSupport::Dependencies.autoload_paths = %w(test/jobs) +    super +  end + +  def after_teardown +    ActiveSupport::Dependencies.autoload_paths = @original_autoload_paths +    super +  end + +  def test_queue_adapter_is_test_adapter +    assert_instance_of ActiveJob::QueueAdapters::TestAdapter, QueueAdapterJob.queue_adapter +  end +end diff --git a/activejob/test/jobs/application_job.rb b/activejob/test/jobs/application_job.rb index 4a78b890ec..a009ace51c 100644 --- a/activejob/test/jobs/application_job.rb +++ b/activejob/test/jobs/application_job.rb @@ -1,4 +1,2 @@ -require_relative "../support/job_buffer" -  class ApplicationJob < ActiveJob::Base  end diff --git a/activejob/test/jobs/queue_adapter_job.rb b/activejob/test/jobs/queue_adapter_job.rb new file mode 100644 index 0000000000..160dfd74ec --- /dev/null +++ b/activejob/test/jobs/queue_adapter_job.rb @@ -0,0 +1,3 @@ +class QueueAdapterJob < ActiveJob::Base +  self.queue_adapter = :inline +end diff --git a/activejob/test/support/integration/adapters/backburner.rb b/activejob/test/support/integration/adapters/backburner.rb index 263097c792..2e194933a1 100644 --- a/activejob/test/support/integration/adapters/backburner.rb +++ b/activejob/test/support/integration/adapters/backburner.rb @@ -23,12 +23,12 @@ module BackburnerJobsManager    end    def tube -    @tube ||= Beaneater::Tube.new(Backburner::Worker.connection, "backburner.worker.queue.integration-tests") # backburner dasherizes the queue name +    @tube ||= Beaneater::Tube.new(@worker.connection, "backburner.worker.queue.integration-tests") # backburner dasherizes the queue name    end    def can_run?      begin -      Backburner::Worker.connection.send :connect! +      @worker = Backburner::Worker.new      rescue        return false      end diff --git a/activejob/test/support/sneakers/inline.rb b/activejob/test/support/sneakers/inline.rb index 3cdc54e6d5..cf102ae5c2 100644 --- a/activejob/test/support/sneakers/inline.rb +++ b/activejob/test/support/sneakers/inline.rb @@ -4,7 +4,7 @@ module Sneakers    module Worker      module ClassMethods        def enqueue(msg) -        worker = self.new(nil, nil, {}) +        worker = new(nil, nil, {})          worker.work(*msg)        end      end diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index 853a1e7d9d..41051b1315 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -1,3 +1,7 @@ +*   Remove unused `ActiveModel::TestCase` class. + +    *Yuji Yaginuma* +  *   Moved DecimalWithoutScale, Text, and UnsignedInteger from Active Model to Active Record      *Iain Beeston* diff --git a/activemodel/MIT-LICENSE b/activemodel/MIT-LICENSE index 8573eb1225..ac810e86d0 100644 --- a/activemodel/MIT-LICENSE +++ b/activemodel/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2016 David Heinemeier Hansson +Copyright (c) 2004-2017 David Heinemeier Hansson  Permission is hereby granted, free of charge, to any person obtaining  a copy of this software and associated documentation files (the diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb index a9b0663940..2389c858d5 100644 --- a/activemodel/lib/active_model.rb +++ b/activemodel/lib/active_model.rb @@ -1,5 +1,5 @@  #-- -# Copyright (c) 2004-2016 David Heinemeier Hansson +# Copyright (c) 2004-2017 David Heinemeier Hansson  #  # Permission is hereby granted, free of charge, to any person obtaining  # a copy of this software and associated documentation files (the @@ -42,7 +42,6 @@ module ActiveModel    autoload :Naming    autoload :SecurePassword    autoload :Serialization -  autoload :TestCase    autoload :Translation    autoload :Validations    autoload :Validator diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb index 9532c63b65..9853cf38fe 100644 --- a/activemodel/lib/active_model/naming.rb +++ b/activemodel/lib/active_model/naming.rb @@ -234,7 +234,7 @@ module ActiveModel      #   Person.model_name.plural   # => "people"      def model_name        @_model_name ||= begin -        namespace = self.parents.detect do |n| +        namespace = parents.detect do |n|            n.respond_to?(:use_relative_model_naming?) && n.use_relative_model_naming?          end          ActiveModel::Name.new(self, namespace) diff --git a/activemodel/lib/active_model/test_case.rb b/activemodel/lib/active_model/test_case.rb deleted file mode 100644 index 5004855d56..0000000000 --- a/activemodel/lib/active_model/test_case.rb +++ /dev/null @@ -1,4 +0,0 @@ -module ActiveModel #:nodoc: -  class TestCase < ActiveSupport::TestCase #:nodoc: -  end -end diff --git a/activemodel/lib/active_model/translation.rb b/activemodel/lib/active_model/translation.rb index b8cf43cc10..35fc7cf743 100644 --- a/activemodel/lib/active_model/translation.rb +++ b/activemodel/lib/active_model/translation.rb @@ -44,7 +44,7 @@ module ActiveModel        parts     = attribute.to_s.split(".")        attribute = parts.pop        namespace = parts.join("/") unless parts.empty? -      attributes_scope = "#{self.i18n_scope}.attributes" +      attributes_scope = "#{i18n_scope}.attributes"        if namespace          defaults = lookup_ancestors.map do |klass| diff --git a/activemodel/lib/active_model/type/decimal.rb b/activemodel/lib/active_model/type/decimal.rb index 6c5c0451c6..541a12c8a1 100644 --- a/activemodel/lib/active_model/type/decimal.rb +++ b/activemodel/lib/active_model/type/decimal.rb @@ -4,6 +4,7 @@ module ActiveModel    module Type      class Decimal < Value # :nodoc:        include Helpers::Numeric +      BIGDECIMAL_PRECISION = 18        def type          :decimal @@ -21,7 +22,7 @@ module ActiveModel              when ::Float                convert_float_to_big_decimal(value)              when ::Numeric, ::String -              BigDecimal(value, precision.to_i) +              BigDecimal(value, precision || BIGDECIMAL_PRECISION)              else                if value.respond_to?(:to_d)                  value.to_d diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb index 8212744170..98234e9b6b 100644 --- a/activemodel/lib/active_model/validator.rb +++ b/activemodel/lib/active_model/validator.rb @@ -141,8 +141,8 @@ module ActiveModel      end      # Performs validation on the supplied record. By default this will call -    # +validates_each+ to determine validity therefore subclasses should -    # override +validates_each+ with validation logic. +    # +validate_each+ to determine validity therefore subclasses should +    # override +validate_each+ with validation logic.      def validate(record)        attributes.each do |attribute|          value = record.read_attribute_for_validation(attribute) diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb index e6ba06301d..0872084cf5 100644 --- a/activemodel/test/cases/errors_test.rb +++ b/activemodel/test/cases/errors_test.rb @@ -1,4 +1,5 @@  require "cases/helper" +require "active_support/core_ext/string/strip"  require "yaml"  class ErrorsTest < ActiveModel::TestCase diff --git a/activemodel/test/cases/helper.rb b/activemodel/test/cases/helper.rb index 4e9f43ad86..eeb5c85a48 100644 --- a/activemodel/test/cases/helper.rb +++ b/activemodel/test/cases/helper.rb @@ -9,15 +9,15 @@ I18n.enforce_available_locales = false  require "active_support/testing/autorun"  require "active_support/testing/method_call_assertions" -# Skips the current run on Rubinius using Minitest::Assertions#skip -def rubinius_skip(message = "") -  skip message if RUBY_ENGINE == "rbx" -end -# Skips the current run on JRuby using Minitest::Assertions#skip -def jruby_skip(message = "") -  skip message if defined?(JRUBY_VERSION) -end - -class ActiveModel::TestCase +class ActiveModel::TestCase < ActiveSupport::TestCase    include ActiveSupport::Testing::MethodCallAssertions + +  # Skips the current run on Rubinius using Minitest::Assertions#skip +  private def rubinius_skip(message = "") +    skip message if RUBY_ENGINE == "rbx" +  end +  # Skips the current run on JRuby using Minitest::Assertions#skip +  private def jruby_skip(message = "") +    skip message if defined?(JRUBY_VERSION) +  end  end diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index eac54e7566..e4fb363da5 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,10 +1,207 @@ +*   Deprecate `ColumnDumper#migration_keys`. + +    *Ryuta Kamizono* + +*   Fix `association_primary_key_type` for reflections with symbol primary key + +    Fixes #27864 + +    *Daniel Colson* + +*   Virtual/generated column support for MySQL 5.7.5+ and MariaDB 5.2.0+. + +    MySQL generated columns: https://dev.mysql.com/doc/refman/5.7/en/create-table-generated-columns.html +    MariaDB virtual columns: https://mariadb.com/kb/en/mariadb/virtual-computed-columns/ + +    Declare virtual columns with `t.virtual name, type: …, as: "expression"`. +    Pass `stored: true` to persist the generated value (false by default). + +    Example: + +        create_table :generated_columns do |t| +          t.string  :name +          t.virtual :upper_name,  type: :string,  as: "UPPER(name)" +          t.virtual :name_length, type: :integer, as: "LENGTH(name)", stored: true +          t.index :name_length  # May be indexed, too! +        end + +    *Ryuta Kamizono* + +*   Deprecate `initialize_schema_migrations_table` and `initialize_internal_metadata_table`. + +    *Ryuta Kamizono* + +*   Support foreign key creation for SQLite3. + +    *Ryuta Kamizono* + +*   Place generated migrations into the path set by `config.paths["db/migrate"]`. + +    *Kevin Glowacz* + +*   Raise `ActiveRecord::InvalidForeignKey` when a foreign key constraint fails on SQLite3. + +    *Ryuta Kamizono* + +*   Add the touch option to ActiveRecord#increment! and decrement!. + +    *Hiroaki Izu* + +*   Deprecate passing a class to the `class_name` because it eagerloads more classes than +    necessary and potentially creates circular dependencies. + +    *Kir Shatrov* + +*   Raise error when has_many through is defined before through association. + +    Fixes #26834. + +    *Chris Holmes* + +*   Deprecate passing `name` to `indexes`. + +    *Ryuta Kamizono* + +*   Remove deprecated tasks: `db:test:clone`, `db:test:clone_schema`, `db:test:clone_structure`. + +    *Rafel Mendonça França* + +*   Compare deserialized values for `PostgreSQL::OID::Hstore` types when +    calling `ActiveRecord::Dirty#changed_in_place?`. + +    Fixes #27502. + +    *Jon Moss* + +*   Raise `ArgumentError` when passing an `ActiveRecord::Base` instance to `.find`, +    `.exists?` and `.update`. + +    *Rafael Mendonça França* + +*   Respect precision option for arrays of timestamps. + +    Fixes #27514. + +    *Sean Griffin* + +*   Optimize slow model instantiation when using STI and `store_full_sti_class = false` option. + +    *Konstantin Lazarev* + +*   Add `touch` option to counter cache modifying methods. + +    Works when updating, resetting, incrementing and decrementing counters: + +        # Touches `updated_at`/`updated_on`. +        Topic.increment_counter(:messages_count, 1, touch: true) +        Topic.decrement_counter(:messages_count, 1, touch: true) + +        # Touches `last_discussed_at`. +        Topic.reset_counters(18, :messages, touch: :last_discussed_at) + +        # Touches `updated_at` and `last_discussed_at`. +        Topic.update_counters(18, messages_count: 5, touch: %i( updated_at last_discussed_at )) + +    Fixes #26724. + +    *Jarred Trost* + +*   Remove deprecated `#uniq`, `#uniq!`, and `#uniq_value`. + +    *Ryuta Kamizono* + +*   Remove deprecated `#insert_sql`, `#update_sql`, and `#delete_sql`. + +    *Ryuta Kamizono* + +*   Remove deprecated `#use_transactional_fixtures` configuration. + +    *Rafael Mendonça França* + +*   Remove deprecated `#raise_in_transactional_callbacks` configuration. + +    *Rafael Mendonça França* + +*   Remove deprecated `#load_schema_for`. + +    *Rafael Mendonça França* + +*   Remove deprecated conditions parameter from `#destroy_all` and `#delete_all`. + +    *Rafael Mendonça França* + +*   Remove deprecated support to passing arguments to `#select` when a block is provided. + +    *Rafael Mendonça França* + +*   Remove deprecated support to query using commas on LIMIT. + +    *Rafael Mendonça França* + +*   Remove deprecated support to passing a class as a value in a query. + +    *Rafael Mendonça França* + +*   Raise `ActiveRecord::IrreversibleOrderError` when using `last` with an irreversible +    order. + +    *Rafael Mendonça França* + +*   Raise when a `has_many :through` association has an ambiguous reflection name. + +    *Rafael Mendonça França* + +*   Raise when `ActiveRecord::Migration` is inherited from directly. + +    *Rafael Mendonça França* + +*   Remove deprecated `original_exception` argument in `ActiveRecord::StatementInvalid#initialize` +    and `ActiveRecord::StatementInvalid#original_exception`. + +    *Rafael Mendonça França* + +*   `#tables` and `#table_exists?` return only tables and not views. + +    All the deprecations on those methods were removed. + +    *Rafael Mendonça França* + +*   Remove deprecated `name` argument from `#tables`. + +    *Rafael Mendonça França* + +*   Remove deprecated support to passing a column to `#quote`. + +    *Rafael Mendonça França* + +*   Set `:time` as a timezone aware type and remove deprecation when +    `config.active_record.time_zone_aware_types` is not explicitly set. + +    *Rafael Mendonça França* + +*   Remove deprecated force reload argument in singular and collection association readers. + +    *Rafael Mendonça França* + +*   Remove deprecated `activerecord.errors.messages.restrict_dependent_destroy.one` and +    `activerecord.errors.messages.restrict_dependent_destroy.many` i18n scopes. + +    *Rafael Mendonça França* + +*   Allow passing extra flags to `db:structure:load` and `db:structure:dump` + +    Introduces `ActiveRecord::Tasks::DatabaseTasks.structure_(load|dump)_flags` to customize the +    eventual commands run against the database, e.g. mysqldump/pg_dump. + +    *Kir Shatrov* +  *   Notifications see frozen SQL string. -    Fixes #23774 +    Fixes #23774.      *Richard Monette* -*   RuntimeErrors are no longer translated to ActiveRecord::StatementInvalid. +*   RuntimeErrors are no longer translated to `ActiveRecord::StatementInvalid`.      *Richard Monette* @@ -62,7 +259,7 @@      *Sean Griffin* -*   Fix that unsigned with zerofill is treated as signed. +*   Don't treat unsigned integers with zerofill as signed.      Fixes #27125. @@ -268,7 +465,7 @@      *Ryuta Kamizono* -*   Sqlite3 migrations to add a column to an existing table can now be +*   SQLite3 migrations to add a column to an existing table can now be      successfully rolled back when the column was given and invalid column      type. diff --git a/activerecord/MIT-LICENSE b/activerecord/MIT-LICENSE index 1f496cf280..f9e4444f07 100644 --- a/activerecord/MIT-LICENSE +++ b/activerecord/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2016 David Heinemeier Hansson +Copyright (c) 2004-2017 David Heinemeier Hansson  Permission is hereby granted, free of charge, to any person obtaining  a copy of this software and associated documentation files (the diff --git a/activerecord/Rakefile b/activerecord/Rakefile index ec28df8fea..7be3d851f1 100644 --- a/activerecord/Rakefile +++ b/activerecord/Rakefile @@ -111,11 +111,6 @@ namespace :db do        config = ARTest.config["connections"]["postgresql"]        %x( createdb -E UTF8 -T template0 #{config["arunit"]["database"]} )        %x( createdb -E UTF8 -T template0 #{config["arunit2"]["database"]} ) - -      # prepare hstore -      if %x( createdb --version ).strip.gsub(/(.*)(\d\.\d\.\d)$/, "\\2") < "9.1.0" -        puts "Please prepare hstore data type. See http://www.postgresql.org/docs/current/static/hstore.html" -      end      end      desc "Drop the PostgreSQL test databases" diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec index 2cd8f179dd..0b37e9076c 100644 --- a/activerecord/activerecord.gemspec +++ b/activerecord/activerecord.gemspec @@ -24,5 +24,5 @@ Gem::Specification.new do |s|    s.add_dependency "activesupport", version    s.add_dependency "activemodel",   version -  s.add_dependency "arel", "~> 7.0" +  s.add_dependency "arel", "~> 8.0"  end diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index 3b5dab16cb..96b8545dfc 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -1,5 +1,5 @@  #-- -# Copyright (c) 2004-2016 David Heinemeier Hansson +# Copyright (c) 2004-2017 David Heinemeier Hansson  #  # Permission is hereby granted, free of charge, to any person obtaining  # a copy of this software and associated documentation files (the @@ -44,7 +44,6 @@ module ActiveRecord    autoload :Explain    autoload :Inheritance    autoload :Integration -  autoload :LegacyYamlAdapter    autoload :Migration    autoload :Migrator, "active_record/migration"    autoload :ModelSchema @@ -85,6 +84,8 @@ module ActiveRecord      autoload :AttributeMethods      autoload :AutosaveAssociation +    autoload :LegacyYamlAdapter +      autoload :Relation      autoload :AssociationRelation      autoload :NullRelation diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 7eb008fc27..4606c91ffd 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -97,6 +97,16 @@ module ActiveRecord      end    end +  class HasManyThroughOrderError < ActiveRecordError #:nodoc: +    def initialize(owner_class_name = nil, reflection = nil, through_reflection = nil) +      if owner_class_name && reflection && through_reflection +        super("Cannot have a has_many :through association '#{owner_class_name}##{reflection.name}' which goes through '#{owner_class_name}##{through_reflection.name}' before the through association is defined.") +      else +        super("Cannot have a has_many :through association before the through association is defined.") +      end +    end +  end +    class ThroughCantAssociateThroughHasOneOrManyReflection < ActiveRecordError #:nodoc:      def initialize(owner = nil, reflection = nil)        if owner && reflection @@ -107,6 +117,21 @@ module ActiveRecord      end    end +  class AmbiguousSourceReflectionForThroughAssociation < ActiveRecordError # :nodoc: +    def initialize(klass, macro, association_name, options, possible_sources) +      example_options = options.dup +      example_options[:source] = possible_sources.first + +      super("Ambiguous source reflection for through association. Please " \ +            "specify a :source directive on your declaration like:\n" \ +            "\n" \ +            "  class #{klass} < ActiveRecord::Base\n" \ +            "    #{macro} :#{association_name}, #{example_options}\n" \ +            "  end" +           ) +    end +  end +    class HasManyThroughCantAssociateThroughHasOneOrManyReflection < ThroughCantAssociateThroughHasOneOrManyReflection #:nodoc:    end @@ -354,23 +379,23 @@ module ActiveRecord        #        # === Overriding generated methods        # -      # Association methods are generated in a module that is included into the model class, -      # which allows you to easily override with your own methods and call the original -      # generated method with +super+. For example: +      # Association methods are generated in a module included into the model +      # class, making overrides easy. The original generated method can thus be +      # called with +super+:        #        #   class Car < ActiveRecord::Base        #     belongs_to :owner        #     belongs_to :old_owner +      #        #     def owner=(new_owner)        #       self.old_owner = self.owner        #       super        #     end        #   end        # -      # If your model class is <tt>Project</tt>, then the module is -      # named <tt>Project::GeneratedAssociationMethods</tt>. The +GeneratedAssociationMethods+ module is -      # included in the model class immediately after the (anonymous) generated attributes methods -      # module, meaning an association will override the methods for an attribute with the same name. +      # The association methods module is included immediately after the +      # generated attributes methods module, meaning an association will +      # override the methods for an attribute with the same name.        #        # == Cardinality and associations        # @@ -1802,7 +1827,7 @@ module ActiveRecord            builder = Builder::HasAndBelongsToMany.new name, self, options -          join_model = builder.through_model +          join_model = ActiveSupport::Deprecation.silence { builder.through_model }            const_set join_model.name, join_model            private_constant join_model.name @@ -1831,8 +1856,8 @@ module ActiveRecord              hm_options[k] = options[k] if options.key? k            end -          has_many name, scope, hm_options, &extension -          self._reflections[name.to_s].parent_reflection = habtm_reflection +          ActiveSupport::Deprecation.silence { has_many name, scope, hm_options, &extension } +          _reflections[name.to_s].parent_reflection = habtm_reflection          end        end    end diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index c6d204d3c2..badde9973f 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -128,9 +128,9 @@ module ActiveRecord            reflection = chain_head            while reflection              table = reflection.alias_name +            next_reflection = reflection.next              unless reflection == chain_tail -              next_reflection = reflection.next                foreign_table = next_reflection.alias_name                scope = next_chain_scope(scope, table, reflection, association_klass, foreign_table, next_reflection)              end @@ -138,7 +138,7 @@ module ActiveRecord              # Exclude the scope of the association itself, because that              # was already merged in the #scope method.              reflection.constraints.each do |scope_chain_item| -              item  = eval_scope(reflection.klass, scope_chain_item, owner) +              item = eval_scope(reflection.klass, table, scope_chain_item, owner)                if scope_chain_item == refl.scope                  scope.merge! item.except(:where, :includes) @@ -153,14 +153,15 @@ module ActiveRecord                scope.order_values |= item.order_values              end -            reflection = reflection.next +            reflection = next_reflection            end            scope          end -        def eval_scope(klass, scope, owner) -          klass.unscoped.instance_exec(owner, &scope) +        def eval_scope(klass, table, scope, owner) +          predicate_builder = PredicateBuilder.new(TableMetadata.new(klass, table)) +          ActiveRecord::Relation.create(klass, table, predicate_builder).instance_exec(owner, &scope)          end      end    end diff --git a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb index 42a90b449c..6b71826431 100644 --- a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb +++ b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb @@ -78,9 +78,9 @@ module ActiveRecord::Associations::Builder # :nodoc:          private -        def self.suppress_composite_primary_key(pk) -          pk unless pk.is_a?(Array) -        end +          def self.suppress_composite_primary_key(pk) +            pk unless pk.is_a?(Array) +          end        }        join_model.name                = "HABTM_#{association_name.to_s.camelize}" diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 46923f690a..0437a79b84 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -25,16 +25,8 @@ module ActiveRecord      # +load_target+ and the +loaded+ flag are your friends.      class CollectionAssociation < Association #:nodoc:        # Implements the reader method, e.g. foo.items for Foo.has_many :items -      def reader(force_reload = false) -        if force_reload -          ActiveSupport::Deprecation.warn(<<-MSG.squish) -            Passing an argument to force an association to reload is now -            deprecated and will be removed in Rails 5.1. Please call `reload` -            on the result collection proxy instead. -          MSG - -          klass.uncached { reload } -        elsif stale_target? +      def reader +        if stale_target?            reload          end @@ -55,9 +47,7 @@ module ActiveRecord        # Implements the ids reader method, e.g. foo.item_ids for Foo.has_many :items        def ids_reader          if loaded? -          load_target.map do |record| -            record.send(reflection.association_primary_key) -          end +          target.pluck(reflection.association_primary_key)          else            @association_ids ||= (              column = "#{reflection.quoted_table_name}.#{reflection.association_primary_key}" @@ -299,16 +289,28 @@ module ActiveRecord        def replace_on_target(record, index, skip_callbacks)          callback(:before_add, record) unless skip_callbacks -        yield(record) if block_given? +        begin +          if index +            record_was = target[index] +            target[index] = record +          else +            target << record +          end -        if index -          @target[index] = record -        else -          append_record(record) +          set_inverse_instance(record) + +          yield(record) if block_given? +        rescue +          if index +            target[index] = record_was +          else +            target.delete(record) +          end + +          raise          end          callback(:after_add, record) unless skip_callbacks -        set_inverse_instance(record)          record        end @@ -502,10 +504,6 @@ module ActiveRecord              load_target.select { |r| ids.include?(r.id.to_s) }            end          end - -        def append_record(record) -          @target << record unless @target.include?(record) -        end      end    end  end diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index 9f91f2b536..0d84805b4d 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -1128,12 +1128,12 @@ module ActiveRecord        private -        def find_nth_with_limit(index, limit) # :doc: +        def find_nth_with_limit(index, limit)            load_target if find_from_target?            super          end -        def find_nth_from_last(index) # :doc: +        def find_nth_from_last(index)            load_target if find_from_target?            super          end diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index 742cd25509..b413eb3f9c 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -16,14 +16,7 @@ module ActiveRecord          when :restrict_with_error            unless empty?              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) -                The error key `:'restrict_dependent_destroy.many'` has been deprecated and will be removed in Rails 5.1. -                Please use `:'restrict_dependent_destroy.has_many'` instead. -              MESSAGE -            end -            owner.errors.add(:base, message || :'restrict_dependent_destroy.has_many', record: record) +            owner.errors.add(:base, :'restrict_dependent_destroy.has_many', record: record)              throw(:abort)            end @@ -38,7 +31,6 @@ module ActiveRecord        def insert_record(record, validate = true, raise = false)          set_owner_attributes(record) -        set_inverse_instance(record)          if raise            record.save!(validate: validate) @@ -108,7 +100,7 @@ module ActiveRecord          end          def delete_or_nullify_all_records(method) -          count = delete_count(method, self.scope) +          count = delete_count(method, scope)            update_counter(-count)          end diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb index 8c90aea975..c4a7fe4432 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -38,10 +38,12 @@ module ActiveRecord        def insert_record(record, validate = true, raise = false)          ensure_not_nested -        if raise -          record.save!(validate: validate) -        else -          return unless record.save(validate: validate) +        if record.new_record? || record.has_changes_to_save? +          if raise +            record.save!(validate: validate) +          else +            return unless record.save(validate: validate) +          end          end          save_through_record(record) @@ -206,10 +208,6 @@ module ActiveRecord          def invertible_for?(record)            false          end - -        def append_record(record) -          @target << record -        end      end    end  end diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb index 21bd668dff..8458253ff8 100644 --- a/activerecord/lib/active_record/associations/has_one_association.rb +++ b/activerecord/lib/active_record/associations/has_one_association.rb @@ -12,14 +12,7 @@ module ActiveRecord          when :restrict_with_error            if load_target              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) -                The error key `:'restrict_dependent_destroy.one'` has been deprecated and will be removed in Rails 5.1. -                Please use `:'restrict_dependent_destroy.has_one'` instead. -              MESSAGE -            end -            owner.errors.add(:base, message || :'restrict_dependent_destroy.has_one', record: record) +            owner.errors.add(:base, :'restrict_dependent_destroy.has_one', record: record)              throw(:abort)            end @@ -92,7 +85,7 @@ module ActiveRecord              if target.persisted? && owner.persisted? && !target.save                set_owner_attributes(target) -              raise RecordNotSaved, "Failed to remove the existing associated #{reflection.name}. " + +              raise RecordNotSaved, "Failed to remove the existing associated #{reflection.name}. " \                                      "The record failed to save after its foreign key was set to nil."              end            end diff --git a/activerecord/lib/active_record/associations/has_one_through_association.rb b/activerecord/lib/active_record/associations/has_one_through_association.rb index 604904abcc..1183bdf6f4 100644 --- a/activerecord/lib/active_record/associations/has_one_through_association.rb +++ b/activerecord/lib/active_record/associations/has_one_through_association.rb @@ -22,6 +22,10 @@ module ActiveRecord            elsif record              attributes = construct_join_attributes(record) +            if through_record && through_record.destroyed? +              through_record = through_proxy.tap(&:reload).target +            end +              if through_record                through_record.update(attributes)              elsif owner.new_record? diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb index 4cd1e64c3d..87e0847ec1 100644 --- a/activerecord/lib/active_record/associations/join_dependency.rb +++ b/activerecord/lib/active_record/associations/join_dependency.rb @@ -32,7 +32,7 @@ module ActiveRecord            @alias_cache[node][column]          end -        class Table < Struct.new(:node, :columns) # :nodoc: +        Table = Struct.new(:node, :columns) do # :nodoc:            def table              Arel::Nodes::TableAlias.new node.table, node.aliased_table_name            end @@ -171,7 +171,7 @@ module ActiveRecord            chain         = child.reflection.chain            foreign_table = parent.table            foreign_klass = parent.base_klass -          child.join_constraints(foreign_table, foreign_klass, child, join_type, tables, child.reflection.scope_chain, chain) +          child.join_constraints(foreign_table, foreign_klass, child, join_type, tables, chain)          end          def make_outer_joins(parent, child) diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb index a5705951f3..f5fcba1236 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb @@ -23,14 +23,11 @@ module ActiveRecord          JoinInformation = Struct.new :joins, :binds -        def join_constraints(foreign_table, foreign_klass, node, join_type, tables, scope_chain, chain) +        def join_constraints(foreign_table, foreign_klass, node, join_type, tables, chain)            joins         = []            binds         = []            tables        = tables.reverse -          scope_chain_index = 0 -          scope_chain = scope_chain.reverse -            # The chain starts with the target table, but we want to end with it here (makes            # more sense in this context), so we reverse            chain.reverse_each do |reflection| @@ -44,7 +41,7 @@ module ActiveRecord              constraint = build_constraint(klass, table, key, foreign_table, foreign_key)              predicate_builder = PredicateBuilder.new(TableMetadata.new(klass, table)) -            scope_chain_items = scope_chain[scope_chain_index].map do |item| +            scope_chain_items = reflection.scopes.map do |item|                if item.is_a?(Relation)                  item                else @@ -52,7 +49,6 @@ module ActiveRecord                    .instance_exec(node, &item)                end              end -            scope_chain_index += 1              klass_scope =                if klass.current_scope diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb index ee7b7c8bea..91580a28d0 100644 --- a/activerecord/lib/active_record/associations/singular_association.rb +++ b/activerecord/lib/active_record/associations/singular_association.rb @@ -2,16 +2,8 @@ module ActiveRecord    module Associations      class SingularAssociation < Association #:nodoc:        # Implements the reader method, e.g. foo.bar for Foo.has_one :bar -      def reader(force_reload = false) -        if force_reload && klass -          ActiveSupport::Deprecation.warn(<<-MSG.squish) -            Passing an argument to force an association to reload is now -            deprecated and will be removed in Rails 5.1. Please call `reload` -            on the parent object instead. -          MSG - -          klass.uncached { reload } -        elsif !loaded? || stale_target? +      def reader +        if !loaded? || stale_target?            reload          end diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb index 07e7bf1a89..6b87993ba3 100644 --- a/activerecord/lib/active_record/associations/through_association.rb +++ b/activerecord/lib/active_record/associations/through_association.rb @@ -10,7 +10,7 @@ module ActiveRecord          #          #   1. To get the default_scope conditions for any of the other reflections in the chain          #   2. To get the type conditions for any STI models in the chain -        def target_scope # :doc: +        def target_scope            scope = super            reflection.chain.drop(1).each do |reflection|              relation = reflection.klass.all diff --git a/activerecord/lib/active_record/attribute.rb b/activerecord/lib/active_record/attribute.rb index 2a8e8f9de4..38281158d8 100644 --- a/activerecord/lib/active_record/attribute.rb +++ b/activerecord/lib/active_record/attribute.rb @@ -135,7 +135,7 @@ module ActiveRecord        attr_reader :original_attribute        alias_method :assigned?, :original_attribute -      def original_value_for_database # :doc: +      def original_value_for_database          if assigned?            original_attribute.original_value_for_database          else @@ -144,17 +144,17 @@ module ActiveRecord        end      private -      def initialize_dup(other) # :doc: +      def initialize_dup(other)          if defined?(@value) && @value.duplicable?            @value = @value.dup          end        end -      def changed_from_assignment? # :doc: +      def changed_from_assignment?          assigned? && type.changed?(original_value, value, value_before_type_cast)        end -      def _original_value_for_database # :doc: +      def _original_value_for_database          type.serialize(original_value)        end diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 1ed1deec55..ebe06566cc 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -394,28 +394,21 @@ module ActiveRecord      protected -      def clone_attribute_value(reader_method, attribute_name) # :nodoc: -        value = send(reader_method, attribute_name) -        value.duplicable? ? value.clone : value -      rescue TypeError, NoMethodError -        value +      def attribute_method?(attr_name) # :nodoc: +        # We check defined? because Syck calls respond_to? before actually calling initialize. +        defined?(@attributes) && @attributes.key?(attr_name)        end -      def arel_attributes_with_values_for_create(attribute_names) # :nodoc: +    private + +      def arel_attributes_with_values_for_create(attribute_names)          arel_attributes_with_values(attributes_for_create(attribute_names))        end -      def arel_attributes_with_values_for_update(attribute_names) # :nodoc: +      def arel_attributes_with_values_for_update(attribute_names)          arel_attributes_with_values(attributes_for_update(attribute_names))        end -      def attribute_method?(attr_name) # :nodoc: -        # We check defined? because Syck calls respond_to? before actually calling initialize. -        defined?(@attributes) && @attributes.key?(attr_name) -      end - -    private -        # Returns a Hash of the Arel::Attributes and attribute values that have been        # typecasted for use in an Arel insert/update method.        def arel_attributes_with_values(attribute_names) diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index e20b65e43c..b0e1391cb9 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -226,6 +226,11 @@ module ActiveRecord          super        end +      def changed?(*) +        emit_warning_if_needed("changed?", "saved_changes?") +        super +      end +        def changed(*)          emit_warning_if_needed("changed", "saved_changes.keys")          super diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb index ffb83a972c..2f32caa257 100644 --- a/activerecord/lib/active_record/attribute_methods/primary_key.rb +++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb @@ -9,7 +9,7 @@ module ActiveRecord        # available.        def to_key          sync_with_transaction_state -        key = self.id +        key = id          [key] if key        end @@ -52,7 +52,7 @@ module ActiveRecord        private -        def attribute_method?(attr_name) # :doc: +        def attribute_method?(attr_name)            attr_name == "id" || super          end diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 727b4288e9..369a6e35aa 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -24,7 +24,7 @@ module ActiveRecord            # to allocate an object on each call to the attribute method.            # Making it frozen means that it doesn't get duped when used to            # key the @attributes in read_attribute. -          def define_method_attribute(name) # :doc: +          def define_method_attribute(name)              safe_name = name.unpack("h*".freeze).first              temp_method = "__temp__#{safe_name}" diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb index 945192fe04..4d9aff76cc 100644 --- a/activerecord/lib/active_record/attribute_methods/serialization.rb +++ b/activerecord/lib/active_record/attribute_methods/serialization.rb @@ -54,7 +54,7 @@ module ActiveRecord            elsif [:load, :dump].all? { |x| class_name_or_coder.respond_to?(x) }              class_name_or_coder            else -            Coders::YAMLColumn.new(class_name_or_coder) +            Coders::YAMLColumn.new(attr_name, class_name_or_coder)            end            decorate_attribute_type(attr_name, :serialize) do |type| diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb index 500d903857..df1231ad47 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -63,7 +63,7 @@ module ActiveRecord          self.skip_time_zone_conversion_for_attributes = []          class_attribute :time_zone_aware_types, instance_writer: false -        self.time_zone_aware_types = [:datetime, :not_explicitly_configured] +        self.time_zone_aware_types = [:datetime, :time]        end        module ClassMethods @@ -86,29 +86,8 @@ module ActiveRecord            def create_time_zone_conversion_attribute?(name, cast_type)              enabled_for_column = time_zone_aware_attributes &&                !skip_time_zone_conversion_for_attributes.include?(name.to_sym) -            result = enabled_for_column && -              time_zone_aware_types.include?(cast_type.type) -            if enabled_for_column && -              !result && -              cast_type.type == :time && -              time_zone_aware_types.include?(:not_explicitly_configured) -              ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc) -                Time columns will become time zone aware in Rails 5.1. This -                still causes `String`s to be parsed as if they were in `Time.zone`, -                and `Time`s to be converted to `Time.zone`. - -                To keep the old behavior, you must add the following to your initializer: - -                    config.active_record.time_zone_aware_types = [:datetime] - -                To silence this deprecation warning, add the following: - -                    config.active_record.time_zone_aware_types = [:datetime, :time] -              MESSAGE -            end - -            result +            enabled_for_column && time_zone_aware_types.include?(cast_type.type)            end        end      end diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb index a80ee74f47..fe0e01db28 100644 --- a/activerecord/lib/active_record/attribute_methods/write.rb +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -10,7 +10,7 @@ module ActiveRecord        module ClassMethods          private -          def define_method_attribute=(name) # :doc: +          def define_method_attribute=(name)              safe_name = name.unpack("h*".freeze).first              ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index 9d0b501862..6bccbc06cd 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -328,9 +328,9 @@ module ActiveRecord        def association_valid?(reflection, record, index = nil)          return true if record.destroyed? || (reflection.options[:autosave] && record.marked_for_destruction?) -        validation_context = self.validation_context unless [:create, :update].include?(self.validation_context) +        context = validation_context unless [:create, :update].include?(validation_context) -        unless valid = record.valid?(validation_context) +        unless valid = record.valid?(context)            if reflection.options[:autosave]              indexed_attribute = !index.nil? && (reflection.options[:index_errors] || ActiveRecord::Base.index_nested_attribute_errors) diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index be6720ddf3..eb44887e18 100644 --- a/activerecord/lib/active_record/callbacks.rb +++ b/activerecord/lib/active_record/callbacks.rb @@ -225,6 +225,55 @@ module ActiveRecord    #    # This way, the +before_destroy+ gets executed before the <tt>dependent: :destroy</tt> is called, and the data is still available.    # +  # Also, there are cases when you want several callbacks of the same type to +  # be executed in order. +  # +  # For example: +  # +  #   class Topic +  #     has_many :children +  # +  #     after_save :log_children +  #     after_save :do_something_else +  # +  #     private +  # +  #     def log_chidren +  #       # Child processing +  #     end +  # +  #     def do_something_else +  #       # Something else +  #     end +  #   end +  # +  # In this case the +log_children+ gets executed before +do_something_else+. +  # The same applies to all non-transactional callbacks. +  # +  # In case there are multiple transactional callbacks as seen below, the order +  # is reversed. +  # +  # For example: +  # +  #   class Topic +  #     has_many :children +  # +  #     after_commit :log_children +  #     after_commit :do_something_else +  # +  #     private +  # +  #     def log_chidren +  #       # Child processing +  #     end +  # +  #     def do_something_else +  #       # Something else +  #     end +  #   end +  # +  # In this case the +do_something_else+ gets executed before +log_children+. +  #    # == \Transactions    #    # The entire callback chain of a {#save}[rdoc-ref:Persistence#save], {#save!}[rdoc-ref:Persistence#save!], diff --git a/activerecord/lib/active_record/coders/yaml_column.rb b/activerecord/lib/active_record/coders/yaml_column.rb index 3a04a10fc9..9c52a31b95 100644 --- a/activerecord/lib/active_record/coders/yaml_column.rb +++ b/activerecord/lib/active_record/coders/yaml_column.rb @@ -5,7 +5,8 @@ module ActiveRecord      class YAMLColumn # :nodoc:        attr_accessor :object_class -      def initialize(object_class = Object) +      def initialize(attr_name, object_class = Object) +        @attr_name = attr_name          @object_class = object_class          check_arity_of_constructor        end @@ -13,7 +14,7 @@ module ActiveRecord        def dump(obj)          return if obj.nil? -        assert_valid_value(obj) +        assert_valid_value(obj, action: "dump")          YAML.dump obj        end @@ -22,27 +23,25 @@ module ActiveRecord          return yaml unless yaml.is_a?(String) && /^---/.match?(yaml)          obj = YAML.load(yaml) -        assert_valid_value(obj) +        assert_valid_value(obj, action: "load")          obj ||= object_class.new if object_class != Object          obj        end -      def assert_valid_value(obj) +      def assert_valid_value(obj, action:)          unless obj.nil? || obj.is_a?(object_class)            raise SerializationTypeMismatch, -            "Attribute was supposed to be a #{object_class}, but was a #{obj.class}. -- #{obj.inspect}" +            "can't #{action} `#{@attr_name}`: was supposed to be a #{object_class}, but was a #{obj.class}. -- #{obj.inspect}"          end        end        private          def check_arity_of_constructor -          begin -            load(nil) -          rescue ArgumentError -            raise ArgumentError, "Cannot serialize #{object_class}. Classes passed to `serialize` must have a 0 argument constructor." -          end +          load(nil) +        rescue ArgumentError +          raise ArgumentError, "Cannot serialize #{object_class}. Classes passed to `serialize` must have a 0 argument constructor."          end      end    end 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 5ec2fc073e..ce4721c99d 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -967,7 +967,7 @@ module ActiveRecord          end          def pool_from_any_process_for(spec_name) -          owner_to_pool = @owner_to_pool.values.find { |v| v[spec_name] } +          owner_to_pool = @owner_to_pool.values.reverse.find { |v| v[spec_name] }            owner_to_pool && owner_to_pool[spec_name]          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 fab5bd0db7..769f488469 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -51,8 +51,7 @@ module ActiveRecord        # Returns a single value from a record        def select_value(arel, name = nil, binds = []) -        arel, binds = binds_from_relation arel, binds -        if result = select_rows(to_sql(arel, binds), name, binds).first +        if result = select_rows(arel, name, binds).first            result.first          end        end @@ -60,14 +59,13 @@ module ActiveRecord        # Returns an array of the values of the first column in a select:        #   select_values("SELECT id FROM companies LIMIT 3") => [1,2,3]        def select_values(arel, name = nil, binds = []) -        arel, binds = binds_from_relation arel, binds -        select_rows(to_sql(arel, binds), name, binds).map(&:first) +        select_rows(arel, name, binds).map(&:first)        end        # Returns an array of arrays containing the field values.        # Order is the same as that returned by +columns+. -      def select_rows(sql, name = nil, binds = []) -        exec_query(sql, name, binds).rows +      def select_rows(arel, name = nil, binds = []) +        select_all(arel, name, binds).rows        end        # Executes the SQL statement in the context of this connection and returns @@ -126,22 +124,16 @@ module ActiveRecord          id_value || last_inserted_id(value)        end        alias create insert -      alias insert_sql insert -      deprecate insert_sql: :insert        # Executes the update statement and returns the number of rows affected.        def update(arel, name = nil, binds = [])          exec_update(to_sql(arel, binds), name, binds)        end -      alias update_sql update -      deprecate update_sql: :update        # Executes the delete statement and returns the number of rows affected.        def delete(arel, name = nil, binds = [])          exec_delete(to_sql(arel, binds), name, binds)        end -      alias delete_sql delete -      deprecate delete_sql: :delete        # Returns +true+ when the connection adapter supports prepared statement        # caching, otherwise returns +false+ @@ -334,17 +326,12 @@ module ActiveRecord        # Sanitizes the given LIMIT parameter in order to prevent SQL injection.        #        # The +limit+ may be anything that can evaluate to a string via #to_s. It -      # should look like an integer, or a comma-delimited list of integers, or -      # an Arel SQL literal. +      # should look like an integer, or an Arel SQL literal.        #        # Returns Integer and Arel::Nodes::SqlLiteral limits as is. -      # Returns the sanitized limit parameter, either as an integer, or as a -      # string which contains a comma-delimited list of integers.        def sanitize_limit(limit)          if limit.is_a?(Integer) || limit.is_a?(Arel::Nodes::SqlLiteral)            limit -        elsif limit.to_s.include?(",") -          Arel.sql limit.to_s.split(",").map { |i| Integer(i) }.join(",")          else            Integer(limit)          end @@ -363,31 +350,31 @@ module ActiveRecord        private          # Returns a subquery for the given key using the join information. -        def subquery_for(key, select) # :doc: +        def subquery_for(key, select)            subselect = select.clone            subselect.projections = [key]            subselect          end          # Returns an ActiveRecord::Result instance. -        def select(sql, name = nil, binds = []) # :doc: +        def select(sql, name = nil, binds = [])            exec_query(sql, name, binds, prepare: false)          end -        def select_prepared(sql, name = nil, binds = []) # :doc: +        def select_prepared(sql, name = nil, binds = [])            exec_query(sql, name, binds, prepare: true)          end -        def sql_for_insert(sql, pk, id_value, sequence_name, binds) # :doc: +        def sql_for_insert(sql, pk, id_value, sequence_name, binds)            [sql, binds]          end -        def last_inserted_id(result) # :doc: +        def last_inserted_id(result)            row = result.rows.first            row && row.first          end -        def binds_from_relation(relation, binds) # :doc: +        def binds_from_relation(relation, binds)            if relation.is_a?(Relation) && binds.empty?              relation, binds = relation.arel, relation.bound_attributes            end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index bbd52b8a91..437e7c6510 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -1,24 +1,15 @@  require "active_support/core_ext/big_decimal/conversions" +require "active_support/multibyte/chars"  module ActiveRecord    module ConnectionAdapters # :nodoc:      module Quoting        # Quotes the column value to help prevent        # {SQL injection attacks}[http://en.wikipedia.org/wiki/SQL_injection]. -      def quote(value, column = nil) +      def quote(value)          # records are quoted as their primary key          return value.quoted_id if value.respond_to?(:quoted_id) -        if column -          ActiveSupport::Deprecation.warn(<<-MSG.squish) -            Passing a column to `quote` has been deprecated. It is only used -            for type casting, which should be handled elsewhere. See -            https://github.com/rails/arel/commit/6160bfbda1d1781c3b08a33ec4955f170e95be11 -            for more information. -          MSG -          value = type_cast_from_column(column, value) -        end -          _quote(value)        end 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 322684672f..81dec97bf7 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb @@ -15,9 +15,9 @@ module ActiveRecord          end          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 +          :options_include_default?, :supports_indexes_in_create?, :supports_foreign_keys_in_create?, :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 +          :options_include_default?, :supports_indexes_in_create?, :supports_foreign_keys_in_create?, :foreign_key_options          private @@ -49,7 +49,7 @@ module ActiveRecord                statements.concat(o.indexes.map { |column_name, options| index_in_create(o.name, column_name, options) })              end -            if supports_foreign_keys? +            if supports_foreign_keys_in_create?                statements.concat(o.foreign_keys.map { |to_table, options| foreign_key_in_create(o.name, to_table, options) })              end @@ -106,6 +106,7 @@ module ActiveRecord              column_options[:primary_key] = o.primary_key              column_options[:collation] = o.collation              column_options[:comment] = o.comment +            column_options[:as] = o.as              column_options            end 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 9b324c090b..ecc6caa8f2 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -3,29 +3,25 @@ module ActiveRecord      # Abstract representation of an index definition on a table. Instances of      # this type are typically created and returned by methods in database      # adapters. e.g. ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter#indexes -    class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using, :comment) #:nodoc: -    end +    IndexDefinition = Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using, :comment) #:nodoc:      # Abstract representation of a column definition. Instances of this type      # are typically created by methods in TableDefinition, and added to the      # +columns+ attribute of said TableDefinition object, in order to be used      # for generating a number of table creation or table changing SQL statements. -    class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :auto_increment, :primary_key, :collation, :sql_type, :comment) #:nodoc: +    ColumnDefinition = Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :auto_increment, :primary_key, :collation, :sql_type, :comment, :as) do # :nodoc:        def primary_key?          primary_key || type.to_sym == :primary_key        end      end -    class AddColumnDefinition < Struct.new(:column) # :nodoc: -    end +    AddColumnDefinition = Struct.new(:column) # :nodoc: -    class ChangeColumnDefinition < Struct.new(:column, :name) #:nodoc: -    end +    ChangeColumnDefinition = Struct.new(:column, :name) #:nodoc: -    class PrimaryKeyDefinition < Struct.new(:name) # :nodoc: -    end +    PrimaryKeyDefinition = Struct.new(:name) # :nodoc: -    class ForeignKeyDefinition < Struct.new(:from_table, :to_table, :options) #:nodoc: +    ForeignKeyDefinition = Struct.new(:from_table, :to_table, :options) do #:nodoc:        def name          options[:name]        end @@ -177,6 +173,7 @@ module ActiveRecord          :text,          :time,          :timestamp, +        :virtual,        ].each do |column_type|          module_eval <<-CODE, __FILE__, __LINE__ + 1            def #{column_type}(*args, **options) @@ -378,6 +375,7 @@ module ActiveRecord          column.primary_key = type == :primary_key || options[:primary_key]          column.collation   = options[:collation]          column.comment     = options[:comment] +        column.as          = options[:as]          column        end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb index b912d24626..34036d8a1d 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb @@ -7,13 +7,15 @@ module ActiveRecord      # Adapter level by over-writing this code inside the database specific adapters      module ColumnDumper        def column_spec(column) -        [schema_type(column), prepare_column_options(column)] +        [schema_type_with_virtual(column), prepare_column_options(column)]        end        def column_spec_for_primary_key(column)          return {} if default_primary_key?(column)          spec = { id: schema_type(column).inspect }          spec.merge!(prepare_column_options(column).except!(:null)) +        spec[:default] ||= "nil" if explicit_primary_key_default?(column) +        spec        end        # This can be overridden on an Adapter level basis to support other @@ -49,9 +51,10 @@ module ActiveRecord        end        # Lists the valid migration options -      def migration_keys -        [:limit, :precision, :scale, :default, :null, :collation, :comment] +      def migration_keys # :nodoc: +        column_options_keys        end +      deprecate :migration_keys        private @@ -59,6 +62,18 @@ module ActiveRecord            schema_type(column) == :bigint          end +        def explicit_primary_key_default?(column) +          false +        end + +        def schema_type_with_virtual(column) +          if supports_virtual_columns? && column.virtual? +            :virtual +          else +            schema_type(column) +          end +        end +          def schema_type(column)            if column.bigint?              :bigint 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 2b131045db..7d602faf30 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -43,7 +43,7 @@ module ActiveRecord        end        # Returns an array of table names defined in the database. -      def tables(name = nil) +      def tables          raise NotImplementedError, "#tables is not implemented"        end @@ -69,7 +69,9 @@ module ActiveRecord        end        # Returns an array of indexes for the given table. -      # def indexes(table_name, name = nil) end +      def indexes(table_name, name = nil) +        raise NotImplementedError, "#indexes is not implemented" +      end        # Checks to see if an index exists on a table for a given index definition.        # @@ -120,7 +122,7 @@ module ActiveRecord          checks = []          checks << lambda { |c| c.name == column_name }          checks << lambda { |c| c.type == type } if type -        migration_keys.each do |attr| +        column_options_keys.each do |attr|            checks << lambda { |c| c.send(attr) == options[attr] } if options.key?(attr)          end @@ -994,27 +996,27 @@ module ActiveRecord        end        def insert_versions_sql(versions) # :nodoc: -        sm_table = ActiveRecord::Migrator.schema_migrations_table_name +        sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name)          if versions.is_a?(Array)            sql = "INSERT INTO #{sm_table} (version) VALUES\n" -          sql << versions.map { |v| "('#{v}')" }.join(",\n") +          sql << versions.map { |v| "(#{quote(v)})" }.join(",\n")            sql << ";\n\n"            sql          else -          "INSERT INTO #{sm_table} (version) VALUES ('#{versions}');" +          "INSERT INTO #{sm_table} (version) VALUES (#{quote(versions)});"          end        end -      # Should not be called normally, but this operation is non-destructive. -      # The migrations module handles this automatically. -      def initialize_schema_migrations_table +      def initialize_schema_migrations_table # :nodoc:          ActiveRecord::SchemaMigration.create_table        end +      deprecate :initialize_schema_migrations_table -      def initialize_internal_metadata_table +      def initialize_internal_metadata_table # :nodoc:          ActiveRecord::InternalMetadata.create_table        end +      deprecate :initialize_internal_metadata_table        def internal_string_options_for_primary_key # :nodoc:          { primary_key: true } @@ -1032,7 +1034,7 @@ module ActiveRecord          end          unless migrated.include?(version) -          execute "INSERT INTO #{sm_table} (version) VALUES ('#{version}')" +          execute "INSERT INTO #{sm_table} (version) VALUES (#{quote(version)})"          end          inserting = (versions - migrated).select { |v| v < version } @@ -1170,8 +1172,11 @@ module ActiveRecord        end        private +        def column_options_keys +          [:limit, :precision, :scale, :default, :null, :collation, :comment] +        end -        def add_index_sort_order(quoted_columns, **options) # :doc: +        def add_index_sort_order(quoted_columns, **options)            if order = options[:order]              case order              when Hash @@ -1186,7 +1191,7 @@ module ActiveRecord          end          # Overridden by the MySQL adapter for supporting index lengths -        def add_options_for_index_columns(quoted_columns, **options) # :doc: +        def add_options_for_index_columns(quoted_columns, **options)            if supports_index_sort_order?              quoted_columns = add_index_sort_order(quoted_columns, options)            end @@ -1194,14 +1199,14 @@ module ActiveRecord            quoted_columns          end -        def quoted_columns_for_index(column_names, **options) # :doc: +        def quoted_columns_for_index(column_names, **options)            return [column_names] if column_names.is_a?(String)            quoted_columns = Hash[column_names.map { |name| [name.to_sym, quote_column_name(name).dup] }]            add_options_for_index_columns(quoted_columns, options).values          end -        def index_name_for_remove(table_name, options = {}) # :doc: +        def index_name_for_remove(table_name, options = {})            return options[:name] if can_remove_index_by_name?(options)            checks = [] @@ -1231,7 +1236,7 @@ module ActiveRecord            end          end -        def rename_table_indexes(table_name, new_name) # :doc: +        def rename_table_indexes(table_name, new_name)            indexes(new_name).each do |index|              generated_index_name = index_name(table_name, column: index.columns)              if generated_index_name == index.name @@ -1240,7 +1245,7 @@ module ActiveRecord            end          end -        def rename_column_indexes(table_name, column_name, new_column_name) # :doc: +        def rename_column_indexes(table_name, column_name, new_column_name)            column_name, new_column_name = column_name.to_s, new_column_name.to_s            indexes(table_name).each do |index|              next unless index.columns.include?(new_column_name) diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 96118c74ca..ae24ce7236 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -176,7 +176,7 @@ module ActiveRecord            if @owner == Thread.current              msg << "it is already leased by the current thread."            else -            msg << "it is already in use by a different thread: #{@owner}. " << +            msg << "it is already in use by a different thread: #{@owner}. " \                     "Current thread: #{Thread.current}."            end            raise ActiveRecordError, msg @@ -194,8 +194,8 @@ module ActiveRecord        def expire          if in_use?            if @owner != Thread.current -            raise ActiveRecordError, "Cannot expire connection, " << -              "it is owned by a different thread: #{@owner}. " << +            raise ActiveRecordError, "Cannot expire connection, " \ +              "it is owned by a different thread: #{@owner}. " \                "Current thread: #{Thread.current}."            end @@ -310,6 +310,12 @@ module ActiveRecord          false        end +      # Does this adapter support creating foreign key constraints +      # in the same statement as creating the table? +      def supports_foreign_keys_in_create? +        supports_foreign_keys? +      end +        # Does this adapter support views?        def supports_views?          false @@ -340,6 +346,11 @@ module ActiveRecord          true        end +      # Does this adapter support virtual columns? +      def supports_virtual_columns? +        false +      end +        # This is meant to be implemented by the adapters that support extensions        def disable_extension(name)        end @@ -571,7 +582,7 @@ module ActiveRecord            end          end -        def translate_exception_class(e, sql) # :doc: +        def translate_exception_class(e, sql)            begin              message = "#{e.class.name}: #{e.message}: #{sql}"            rescue Encoding::CompatibilityError @@ -596,7 +607,7 @@ module ActiveRecord            raise translate_exception_class(e, sql)          end -        def translate_exception(exception, message) # :doc: +        def translate_exception(exception, message)            # override in derived class            case exception            when RuntimeError @@ -606,7 +617,7 @@ module ActiveRecord            end          end -        def without_prepared_statement?(binds) # :doc: +        def without_prepared_statement?(binds)            !prepared_statements || binds.empty?          end 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 d553a0b0f7..18e227b40d 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -67,8 +67,8 @@ module ActiveRecord          @statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit])) -        if version < "5.0.0" -          raise "Your version of MySQL (#{full_version.match(/^\d+\.\d+\.\d+/)[0]}) is too old. Active Record supports MySQL >= 5.0." +        if version < "5.1.10" +          raise "Your version of MySQL (#{full_version.match(/^\d+\.\d+\.\d+/)[0]}) is too old. Active Record supports MySQL >= 5.1.10."          end        end @@ -141,6 +141,14 @@ module ActiveRecord          end        end +      def supports_virtual_columns? +        if mariadb? +          version >= "5.2.0" +        else +          version >= "5.7.5" +        end +      end +        def supports_advisory_locks?          true        end @@ -310,45 +318,36 @@ module ActiveRecord          show_variable "collation_database"        end -      def tables(name = nil) # :nodoc: -        ActiveSupport::Deprecation.warn(<<-MSG.squish) -          #tables currently returns both tables and views. -          This behavior is deprecated and will be changed with Rails 5.1 to only return tables. -          Use #data_sources instead. -        MSG +      def tables # :nodoc: +        sql = "SELECT table_name FROM information_schema.tables WHERE table_type = 'BASE TABLE'" +        sql << " AND table_schema = #{quote(@config[:database])}" -        if name -          ActiveSupport::Deprecation.warn(<<-MSG.squish) -            Passing arguments to #tables is deprecated without replacement. -          MSG -        end +        select_values(sql, "SCHEMA") +      end -        data_sources +      def views # :nodoc: +        select_values("SHOW FULL TABLES WHERE table_type = 'VIEW'", "SCHEMA")        end -      def data_sources +      def data_sources # :nodoc:          sql = "SELECT table_name FROM information_schema.tables "          sql << "WHERE table_schema = #{quote(@config[:database])}"          select_values(sql, "SCHEMA")        end -      def truncate(table_name, name = nil) -        execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name -      end +      def table_exists?(table_name) # :nodoc: +        return false unless table_name.present? -      def table_exists?(table_name) -        # Update lib/active_record/internal_metadata.rb when this gets removed -        ActiveSupport::Deprecation.warn(<<-MSG.squish) -          #table_exists? currently checks both tables and views. -          This behavior is deprecated and will be changed with Rails 5.1 to only check tables. -          Use #data_source_exists? instead. -        MSG +        schema, name = extract_schema_qualified_name(table_name) + +        sql = "SELECT table_name FROM information_schema.tables WHERE table_type = 'BASE TABLE'" +        sql << " AND table_schema = #{quote(schema)} AND table_name = #{quote(name)}" -        data_source_exists?(table_name) +        select_values(sql, "SCHEMA").any?        end -      def data_source_exists?(table_name) +      def data_source_exists?(table_name) # :nodoc:          return false unless table_name.present?          schema, name = extract_schema_qualified_name(table_name) @@ -359,10 +358,6 @@ module ActiveRecord          select_values(sql, "SCHEMA").any?        end -      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? @@ -374,8 +369,18 @@ module ActiveRecord          select_values(sql, "SCHEMA").any?        end +      def truncate(table_name, name = nil) +        execute "TRUNCATE TABLE #{quote_table_name(table_name)}", name +      end +        # Returns an array of indexes for the given table.        def indexes(table_name, name = nil) #:nodoc: +        if name +          ActiveSupport::Deprecation.warn(<<-MSG.squish) +            Passing name to #indexes is deprecated without replacement. +          MSG +        end +          indexes = []          current_index = nil          execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", "SCHEMA") do |result| @@ -530,6 +535,7 @@ module ActiveRecord            WHERE fk.referenced_column_name IS NOT NULL              AND fk.table_schema = #{quote(schema)}              AND fk.table_name = #{quote(name)} +            AND rc.table_name = #{quote(name)}          SQL          fk_info.map do |row| @@ -701,7 +707,7 @@ module ActiveRecord            end          end -        def extract_precision(sql_type) # :doc: +        def extract_precision(sql_type)            if /time/.match?(sql_type)              super || 0            else @@ -709,11 +715,11 @@ module ActiveRecord            end          end -        def fetch_type_metadata(sql_type, extra = "") # :doc: +        def fetch_type_metadata(sql_type, extra = "")            MySQL::TypeMetadata.new(super(sql_type), extra: extra)          end -        def add_index_length(quoted_columns, **options) # :doc: +        def add_index_length(quoted_columns, **options)            if length = options[:length]              case length              when Hash @@ -727,7 +733,7 @@ module ActiveRecord            quoted_columns          end -        def add_options_for_index_columns(quoted_columns, **options) # :doc: +        def add_options_for_index_columns(quoted_columns, **options)            quoted_columns = add_index_length(quoted_columns, options)            super          end @@ -743,7 +749,7 @@ module ActiveRecord          ER_CANNOT_ADD_FOREIGN   = 1215          ER_CANNOT_CREATE_TABLE  = 1005 -        def translate_exception(exception, message) # :doc: +        def translate_exception(exception, message)            case error_number(exception)            when ER_DUP_ENTRY              RecordNotUnique.new(message) @@ -770,13 +776,13 @@ module ActiveRecord            end          end -        def add_column_sql(table_name, column_name, type, options = {}) # :doc: +        def add_column_sql(table_name, column_name, type, options = {})            td = create_table_definition(table_name)            cd = td.new_column_definition(column_name, type, options)            schema_creation.accept(AddColumnDefinition.new(cd))          end -        def change_column_sql(table_name, column_name, type, options = {}) # :doc: +        def change_column_sql(table_name, column_name, type, options = {})            column = column_for(table_name, column_name)            unless options_include_default?(options) @@ -796,7 +802,7 @@ module ActiveRecord            schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))          end -        def rename_column_sql(table_name, column_name, new_column_name) # :doc: +        def rename_column_sql(table_name, column_name, new_column_name)            column  = column_for(table_name, column_name)            options = {              default: column.default, @@ -810,30 +816,30 @@ module ActiveRecord            schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))          end -        def remove_column_sql(table_name, column_name, type = nil, options = {}) # :doc: +        def remove_column_sql(table_name, column_name, type = nil, options = {})            "DROP #{quote_column_name(column_name)}"          end -        def remove_columns_sql(table_name, *column_names) # :doc: +        def remove_columns_sql(table_name, *column_names)            column_names.map { |column_name| remove_column_sql(table_name, column_name) }          end -        def add_index_sql(table_name, column_name, options = {}) # :doc: +        def add_index_sql(table_name, column_name, options = {})            index_name, index_type, index_columns, _, index_algorithm, index_using = add_index_options(table_name, column_name, options)            index_algorithm[0, 0] = ", " if index_algorithm.present?            "ADD #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_algorithm}"          end -        def remove_index_sql(table_name, options = {}) # :doc: +        def remove_index_sql(table_name, options = {})            index_name = index_name_for_remove(table_name, options)            "DROP INDEX #{index_name}"          end -        def add_timestamps_sql(table_name, options = {}) # :doc: +        def add_timestamps_sql(table_name, options = {})            [add_column_sql(table_name, :created_at, :datetime, options), add_column_sql(table_name, :updated_at, :datetime, options)]          end -        def remove_timestamps_sql(table_name, options = {}) # :doc: +        def remove_timestamps_sql(table_name, options = {})            [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)]          end @@ -906,7 +912,7 @@ module ActiveRecord            end.compact.join(", ")            # ...and send them all in one query -          @connection.query "SET #{encoding} #{sql_mode_assignment} #{variable_assignments}" +          execute "SET #{encoding} #{sql_mode_assignment} #{variable_assignments}"          end          def column_definitions(table_name) # :nodoc: diff --git a/activerecord/lib/active_record/connection_adapters/mysql/column.rb b/activerecord/lib/active_record/connection_adapters/mysql/column.rb index 1499c1681f..c9ad47c035 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/column.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/column.rb @@ -15,6 +15,10 @@ module ActiveRecord          def auto_increment?            extra == "auto_increment"          end + +        def virtual? +          /\b(?:VIRTUAL|STORED|PERSISTENT)\b/.match?(extra) +        end        end      end    end diff --git a/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb index 1e13890eca..8c67a7a80b 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb @@ -3,7 +3,7 @@ module ActiveRecord      module MySQL        module DatabaseStatements          # Returns an ActiveRecord::Result instance. -        def select_all(arel, name = nil, binds = [], preparable: nil) +        def select_all(arel, name = nil, binds = [], preparable: nil) # :nodoc:            result = if ExplainRegistry.collect? && prepared_statements              unprepared_statement { super }            else @@ -15,8 +15,8 @@ module ActiveRecord          # Returns an array of arrays containing the field values.          # Order is the same as that returned by +columns+. -        def select_rows(sql, name = nil, binds = []) -          select_result(sql, name, binds) do |result| +        def select_rows(arel, name = nil, binds = []) # :nodoc: +          select_result(arel, name, binds) do |result|              @connection.next_result while @connection.more_results?              result.to_a            end @@ -54,11 +54,13 @@ module ActiveRecord          private -          def last_inserted_id(result) # :doc: +          def last_inserted_id(result)              @connection.last_id            end -          def select_result(sql, name = nil, binds = []) +          def select_result(arel, name, binds) +            arel, binds = binds_from_relation(arel, binds) +            sql = to_sql(arel, binds)              if without_prepared_statement?(binds)                execute_and_free(sql, name) { |result| yield result }              else diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb index d808b50332..39c2acbca9 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb @@ -2,8 +2,8 @@ module ActiveRecord    module ConnectionAdapters      module MySQL        class SchemaCreation < AbstractAdapter::SchemaCreation -        delegate :add_sql_comment!, to: :@conn -        private :add_sql_comment! +        delegate :add_sql_comment!, :mariadb?, to: :@conn +        private :add_sql_comment!, :mariadb?          private @@ -32,6 +32,7 @@ module ActiveRecord            def column_options(o)              column_options = super              column_options[:charset] = o.charset +            column_options[:stored] = o.stored              column_options            end @@ -44,6 +45,13 @@ module ActiveRecord                sql << " COLLATE #{collation}"              end +            if as = options[:as] +              sql << " AS (#{as})" +              if options[:stored] +                sql << (mariadb? ? " PERSISTENT" : " STORED") +              end +            end +              add_sql_comment!(super, options[:comment])            end diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb index 0cf40de70f..76ebd0bf6c 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb @@ -3,10 +3,7 @@ module ActiveRecord      module MySQL        module ColumnMethods          def primary_key(name, type = :primary_key, **options) -          if type == :primary_key && !options.key?(:default) -            options[:auto_increment] = true -            options[:limit] = 8 -          end +          options[:auto_increment] = true if [:integer, :bigint].include?(type) && !options.key?(:default)            super          end @@ -60,24 +57,29 @@ module ActiveRecord        end        class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition -        attr_accessor :charset, :unsigned +        attr_accessor :charset, :unsigned, :stored        end        class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition          include ColumnMethods          def new_column_definition(name, type, options) # :nodoc: -          column = super -          case column.type +          case type +          when :virtual +            type = options[:type]            when :primary_key -            column.type = :integer -            column.auto_increment = true +            type = :integer +            options[:limit] ||= 8 +            options[:auto_increment] = true +            options[:primary_key] = true            when /\Aunsigned_(?<type>.+)\z/ -            column.type = $~[:type].to_sym -            column.unsigned = true +            type = $~[:type].to_sym +            options[:unsigned] = true            end -          column.unsigned ||= options[:unsigned] +          column = super +          column.unsigned = options[:unsigned]            column.charset = options[:charset] +          column.stored = options[:stored]            column          end diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb index 2065816501..a06dd0f6b8 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb @@ -1,19 +1,17 @@  module ActiveRecord    module ConnectionAdapters      module MySQL -      module ColumnDumper -        def column_spec_for_primary_key(column) -          spec = super -          if column.type == :integer && !column.auto_increment? -            spec[:default] = schema_default(column) || "nil" -          end -          spec[:unsigned] = "true" if column.unsigned? -          spec -        end - +      module ColumnDumper # :nodoc:          def prepare_column_options(column)            spec = super            spec[:unsigned] = "true" if column.unsigned? + +          if supports_virtual_columns? && column.virtual? +            spec[:as] = extract_expression_for_virtual_column(column) +            spec[:stored] = "true" if /\b(?:STORED|PERSISTENT)\b/.match?(column.extra) +            spec = { type: schema_type(column).inspect }.merge!(spec) +          end +            spec          end @@ -27,6 +25,10 @@ module ActiveRecord              super && column.auto_increment?            end +          def explicit_primary_key_default?(column) +            column.type == :integer && !column.auto_increment? +          end +            def schema_type(column)              if column.sql_type == "tinyblob"                :blob @@ -46,6 +48,21 @@ module ActiveRecord                column.collation.inspect if column.collation != @table_collation_cache[table_name]              end            end + +          def extract_expression_for_virtual_column(column) +            if mariadb? +              create_table_info = create_table_info(column.table_name) +              if %r/#{quote_column_name(column.name)} #{Regexp.quote(column.sql_type)} AS \((?<expression>.+?)\) #{column.extra}/m =~ create_table_info +                $~[:expression].inspect +              end +            else +              sql = "SELECT generation_expression FROM information_schema.columns" \ +                    " WHERE table_schema = #{quote(@config[:database])}" \ +                    "   AND table_name = #{quote(column.table_name)}" \ +                    "   AND column_name = #{quote(column.name)}" +              select_value(sql, "SCHEMA").inspect +            end +          end        end      end    end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb index 520a50506f..705e6063dc 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb @@ -7,18 +7,14 @@ module ActiveRecord            PostgreSQL::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", binds))          end -        def select_value(arel, name = nil, binds = []) -          arel, binds = binds_from_relation arel, binds -          sql = to_sql(arel, binds) -          execute_and_clear(sql, name, binds) do |result| +        def select_value(arel, name = nil, binds = []) # :nodoc: +          select_result(arel, name, binds) do |result|              result.getvalue(0, 0) if result.ntuples > 0 && result.nfields > 0            end          end -        def select_values(arel, name = nil, binds = []) -          arel, binds = binds_from_relation arel, binds -          sql = to_sql(arel, binds) -          execute_and_clear(sql, name, binds) do |result| +        def select_values(arel, name = nil, binds = []) # :nodoc: +          select_result(arel, name, binds) do |result|              if result.nfields > 0                result.column_values(0)              else @@ -29,8 +25,8 @@ module ActiveRecord          # Executes a SELECT query and returns an array of rows. Each row is an          # array of field values. -        def select_rows(sql, name = nil, binds = []) -          execute_and_clear(sql, name, binds) do |result| +        def select_rows(arel, name = nil, binds = []) # :nodoc: +          select_result(arel, name, binds) do |result|              result.values            end          end @@ -134,7 +130,7 @@ module ActiveRecord            super          end -        protected :sql_for_insert +        private :sql_for_insert          def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil)            if use_insert_returning? || pk == false @@ -179,6 +175,14 @@ module ActiveRecord            def suppress_composite_primary_key(pk)              pk unless pk.is_a?(Array)            end + +          def select_result(arel, name, binds) +            arel, binds = binds_from_relation(arel, binds) +            sql = to_sql(arel, binds) +            execute_and_clear(sql, name, binds) do |result| +              yield result +            end +          end        end      end    end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb index d9daaaa23e..e1a75f8e5e 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb @@ -5,8 +5,10 @@ module ActiveRecord          class Array < Type::Value # :nodoc:            include Type::Helpers::Mutable +          Data = Struct.new(:encoder, :values) # :nodoc: +            attr_reader :subtype, :delimiter -          delegate :type, :user_input_in_time_zone, :limit, to: :subtype +          delegate :type, :user_input_in_time_zone, :limit, :precision, :scale, to: :subtype            def initialize(subtype, delimiter = ",")              @subtype = subtype @@ -17,8 +19,11 @@ module ActiveRecord            end            def deserialize(value) -            if value.is_a?(::String) +            case value +            when ::String                type_cast_array(@pg_decoder.decode(value), :deserialize) +            when Data +              deserialize(value.values)              else                super              end @@ -33,11 +38,8 @@ module ActiveRecord            def serialize(value)              if value.is_a?(::Array) -              result = @pg_encoder.encode(type_cast_array(value, :serialize)) -              if encoding = determine_encoding_of_strings(value) -                result.force_encoding(encoding) -              end -              result +              casted_values = type_cast_array(value, :serialize) +              Data.new(@pg_encoder, casted_values)              else                super              end @@ -58,6 +60,10 @@ module ActiveRecord              value.map(&block)            end +          def changed_in_place?(raw_old_value, new_value) +            deserialize(raw_old_value) != new_value +          end +            private              def type_cast_array(value, method) @@ -67,13 +73,6 @@ module ActiveRecord                  @subtype.public_send(method, value)                end              end - -            def determine_encoding_of_strings(value) -              case value -              when ::Array then determine_encoding_of_strings(value.first) -              when ::String then value.encoding -              end -            end          end        end      end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb index d629ebca91..49dd4fc73f 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb @@ -35,6 +35,14 @@ module ActiveRecord              ActiveRecord::Store::StringKeyedHashAccessor            end +          # Will compare the Hash equivalents of +raw_old_value+ and +new_value+. +          # By comparing hashes, this avoids an edge case where the order of +          # the keys change between the two hashes, and they would not be marked +          # as equal. +          def changed_in_place?(raw_old_value, new_value) +            deserialize(raw_old_value) != new_value +          end +            private              HstorePair = begin diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb index 2c714f4018..54d5d0902e 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb @@ -1,5 +1,3 @@ -require "active_support/core_ext/string/filters" -  module ActiveRecord    module ConnectionAdapters      module PostgreSQL diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb index b5031d890f..3783925954 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -92,6 +92,8 @@ module ActiveRecord                else                  super                end +            when OID::Array::Data +              _quote(encode_array(value))              else                super              end @@ -106,10 +108,37 @@ module ActiveRecord                { value: value.to_s, format: 1 }              when OID::Xml::Data, OID::Bit::Data                value.to_s +            when OID::Array::Data +              encode_array(value)              else                super              end            end + +          def encode_array(array_data) +            encoder = array_data.encoder +            values = type_cast_array(array_data.values) + +            result = encoder.encode(values) +            if encoding = determine_encoding_of_strings_in_array(values) +              result.force_encoding(encoding) +            end +            result +          end + +          def determine_encoding_of_strings_in_array(value) +            case value +            when ::Array then determine_encoding_of_strings_in_array(value.first) +            when ::String then value.encoding +            end +          end + +          def type_cast_array(values) +            case values +            when ::Array then values.map { |item| type_cast_array(item) } +            else _type_cast(values) +            end +          end        end      end    end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb index 4afb4733eb..0443bd8bdd 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb @@ -42,6 +42,7 @@ module ActiveRecord          # a record (as primary keys cannot be +nil+). This might be done via the          # +SecureRandom.uuid+ method and a +before_save+ callback, for instance.          def primary_key(name, type = :primary_key, **options) +          options[:auto_increment] = true if [:integer, :bigint].include?(type) && !options.key?(:default)            if type == :uuid              options[:default] = options.fetch(:default, "gen_random_uuid()")            elsif options.delete(:auto_increment) == true && %i(integer bigint).include?(type) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb index 7808d37deb..5555a46b6b 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb @@ -1,15 +1,7 @@  module ActiveRecord    module ConnectionAdapters      module PostgreSQL -      module ColumnDumper -        def column_spec_for_primary_key(column) -          spec = super -          if schema_type(column) == :uuid -            spec[:default] ||= "nil" -          end -          spec -        end - +      module ColumnDumper # :nodoc:          # Adds +:array+ option to the default set          def prepare_column_options(column)            spec = super @@ -28,6 +20,10 @@ module ActiveRecord              schema_type(column) == :bigserial            end +          def explicit_primary_key_default?(column) +            column.type == :uuid || (column.type == :integer && !column.serial?) +          end +            def schema_type(column)              return super unless column.serial? 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 9e7487b27f..bfda113e40 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -71,13 +71,7 @@ module ActiveRecord          end          # Returns the list of all tables in the schema search path. -        def tables(name = nil) -          if name -            ActiveSupport::Deprecation.warn(<<-MSG.squish) -              Passing arguments to #tables is deprecated without replacement. -            MSG -          end - +        def tables            select_values("SELECT tablename FROM pg_tables WHERE schemaname = ANY(current_schemas(false))", "SCHEMA")          end @@ -91,40 +85,42 @@ 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 +          # Returns true if table exists.          # If the schema is not specified as part of +name+ then it will only find tables within          # the current schema search path (regardless of permissions to access tables in other schemas)          def table_exists?(name) -          ActiveSupport::Deprecation.warn(<<-MSG.squish) -            #table_exists? currently checks both tables and views. -            This behavior is deprecated and will be changed with Rails 5.1 to only check tables. -            Use #data_source_exists? instead. -          MSG - -          data_source_exists?(name) -        end - -        def data_source_exists?(name)            name = Utils.extract_schema_qualified_name(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 ('r','v','m') -- (r)elation/table, (v)iew, (m)aterialized view -              AND c.relname = #{quote(name.identifier)} -              AND n.nspname = #{name.schema ? quote(name.schema) : "ANY (current_schemas(false))"} +            SELECT tablename +            FROM pg_tables +            WHERE tablename = #{quote(name.identifier)} +            AND schemaname = #{name.schema ? quote(name.schema) : "ANY (current_schemas(false))"}            SQL          end -        def views # :nodoc: -          select_values(<<-SQL, "SCHEMA") +        def data_source_exists?(name) # :nodoc: +          name = Utils.extract_schema_qualified_name(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 n.nspname = ANY (current_schemas(false)) +            WHERE c.relkind IN ('r','v','m') -- (r)elation/table, (v)iew, (m)aterialized view +            AND c.relname = #{quote(name.identifier)} +            AND n.nspname = #{name.schema ? quote(name.schema) : "ANY (current_schemas(false))"}            SQL          end @@ -170,7 +166,13 @@ module ActiveRecord          end          # Returns an array of indexes for the given table. -        def indexes(table_name, name = nil) +        def indexes(table_name, name = nil) # :nodoc: +          if name +            ActiveSupport::Deprecation.warn(<<-MSG.squish) +              Passing name to #indexes is deprecated without replacement. +            MSG +          end +            table = Utils.extract_schema_qualified_name(table_name.to_s)            result = query(<<-SQL, "SCHEMA") diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 72947a78f5..a2c5ef6817 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -415,7 +415,7 @@ module ActiveRecord          SERIALIZATION_FAILURE = "40001"          DEADLOCK_DETECTED     = "40P01" -        def translate_exception(exception, message) # :doc: +        def translate_exception(exception, message)            return exception unless exception.respond_to?(:result)            case exception.result.try(:error_field, PGresult::PG_DIAG_SQLSTATE) @@ -438,7 +438,7 @@ module ActiveRecord            end          end -        def get_oid_type(oid, fmod, column_name, sql_type = "") +        def get_oid_type(oid, fmod, column_name, sql_type = "".freeze)            if !type_map.key?(oid)              load_additional_types(type_map, [oid])            end @@ -759,11 +759,11 @@ module ActiveRecord            query(<<-end_sql, "SCHEMA")                SELECT a.attname, format_type(a.atttypid, a.atttypmod),                       pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod, -             (SELECT c.collname FROM pg_collation c, pg_type t -               WHERE c.oid = a.attcollation AND t.oid = a.atttypid AND a.attcollation <> t.typcollation), -                     col_description(a.attrelid, a.attnum) AS comment -                FROM pg_attribute a LEFT JOIN pg_attrdef d -                  ON a.attrelid = d.adrelid AND a.attnum = d.adnum +                     c.collname, col_description(a.attrelid, a.attnum) AS comment +                FROM pg_attribute a +                LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum +                LEFT JOIN pg_type t ON a.atttypid = t.oid +                LEFT JOIN pg_collation c ON a.attcollation = c.oid AND a.attcollation <> t.typcollation                 WHERE a.attrelid = #{quote(quote_table_name(table_name))}::regclass                   AND a.attnum > 0 AND NOT a.attisdropped                 ORDER BY a.attnum diff --git a/activerecord/lib/active_record/connection_adapters/schema_cache.rb b/activerecord/lib/active_record/connection_adapters/schema_cache.rb index 3a319c4029..4d339b0a8c 100644 --- a/activerecord/lib/active_record/connection_adapters/schema_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/schema_cache.rb @@ -48,8 +48,6 @@ module ActiveRecord          @data_sources[name] = connection.data_source_exists?(name)        end -      alias table_exists? data_source_exists? -      deprecate table_exists?: "use #data_source_exists? instead"        # Add internal cache for table with +table_name+.        def add(table_name) @@ -63,8 +61,6 @@ module ActiveRecord        def data_sources(name)          @data_sources[name]        end -      alias tables data_sources -      deprecate tables: "use #data_sources instead"        # Get the columns for a table        def columns(table_name) @@ -99,8 +95,6 @@ module ActiveRecord          @primary_keys.delete name          @data_sources.delete name        end -      alias clear_table_cache! clear_data_source_cache! -      deprecate clear_table_cache!: "use #clear_data_source_cache! instead"        def marshal_dump          # if we get current version during initialization, it happens stack over flow. diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb index d0b38dff4c..f9bb7e6d82 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb @@ -3,7 +3,7 @@ module ActiveRecord      module SQLite3        module ColumnMethods          def primary_key(name, type = :primary_key, **options) -          if options.delete(:auto_increment) == true && %i(integer bigint).include?(type) +          if %i(integer bigint).include?(type) && (options.delete(:auto_increment) == true || !options.key?(:default))              type = :primary_key            end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb index c027fef83c..eec018eda3 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb @@ -1,12 +1,16 @@  module ActiveRecord    module ConnectionAdapters      module SQLite3 -      module ColumnDumper +      module ColumnDumper # :nodoc:          private            def default_primary_key?(column)              schema_type(column) == :integer            end + +          def explicit_primary_key_default?(column) +            column.bigint? +          end        end      end    end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index f2c84cd782..ca6de37a6b 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -95,6 +95,8 @@ module ActiveRecord          @active     = nil          @statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit])) + +        configure_connection        end        def supports_ddl_transactions? @@ -128,6 +130,10 @@ module ActiveRecord          true        end +      def supports_foreign_keys_in_create? +        sqlite_version >= "3.6.19" +      end +        def supports_views?          true        end @@ -185,6 +191,19 @@ module ActiveRecord          true        end +      # REFERENTIAL INTEGRITY ==================================== + +      def disable_referential_integrity # :nodoc: +        old = select_value("PRAGMA foreign_keys") + +        begin +          execute("PRAGMA foreign_keys = OFF") +          yield +        ensure +          execute("PRAGMA foreign_keys = #{old}") +        end +      end +        #--        # DATABASE STATEMENTS ======================================        #++ @@ -259,47 +278,34 @@ module ActiveRecord        # SCHEMA STATEMENTS ======================================== -      def tables(name = nil) # :nodoc: -        ActiveSupport::Deprecation.warn(<<-MSG.squish) -          #tables currently returns both tables and views. -          This behavior is deprecated and will be changed with Rails 5.1 to only return tables. -          Use #data_sources instead. -        MSG - -        if name -          ActiveSupport::Deprecation.warn(<<-MSG.squish) -            Passing arguments to #tables is deprecated without replacement. -          MSG -        end - -        data_sources +      def tables # :nodoc: +        select_values("SELECT name FROM sqlite_master WHERE type = 'table' AND name <> 'sqlite_sequence'", "SCHEMA")        end -      def data_sources +      def data_sources # :nodoc:          select_values("SELECT name FROM sqlite_master WHERE type IN ('table','view') AND name <> 'sqlite_sequence'", "SCHEMA")        end -      def table_exists?(table_name) -        ActiveSupport::Deprecation.warn(<<-MSG.squish) -          #table_exists? currently checks both tables and views. -          This behavior is deprecated and will be changed with Rails 5.1 to only check tables. -          Use #data_source_exists? instead. -        MSG - -        data_source_exists?(table_name) +      def views # :nodoc: +        select_values("SELECT name FROM sqlite_master WHERE type = 'view' AND name <> 'sqlite_sequence'", "SCHEMA")        end -      def data_source_exists?(table_name) +      def table_exists?(table_name) # :nodoc:          return false unless table_name.present? -        sql = "SELECT name FROM sqlite_master WHERE type IN ('table','view') AND name <> 'sqlite_sequence'" +        sql = "SELECT name FROM sqlite_master WHERE type = 'table' AND name <> 'sqlite_sequence'"          sql << " AND name = #{quote(table_name)}"          select_values(sql, "SCHEMA").any?        end -      def views # :nodoc: -        select_values("SELECT name FROM sqlite_master WHERE type = 'view' AND name <> 'sqlite_sequence'", "SCHEMA") +      def data_source_exists?(table_name) # :nodoc: +        return false unless table_name.present? + +        sql = "SELECT name FROM sqlite_master WHERE type IN ('table','view') AND name <> 'sqlite_sequence'" +        sql << " AND name = #{quote(table_name)}" + +        select_values(sql, "SCHEMA").any?        end        def view_exists?(view_name) # :nodoc: @@ -329,6 +335,12 @@ module ActiveRecord        # Returns an array of indexes for the given table.        def indexes(table_name, name = nil) #:nodoc: +        if name +          ActiveSupport::Deprecation.warn(<<-MSG.squish) +            Passing name to #indexes is deprecated without replacement. +          MSG +        end +          exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", "SCHEMA").map do |row|            sql = <<-SQL              SELECT sql @@ -431,6 +443,19 @@ module ActiveRecord          rename_column_indexes(table_name, column.name, new_column_name)        end +      def foreign_keys(table_name) +        fk_info = select_all("PRAGMA foreign_key_list(#{quote(table_name)})", "SCHEMA") +        fk_info.map do |row| +          options = { +            column: row["from"], +            primary_key: row["to"], +            on_delete: extract_foreign_key_action(row["on_delete"]), +            on_update: extract_foreign_key_action(row["on_update"]) +          } +          ForeignKeyDefinition.new(table_name, row["table"], options) +        end +      end +        private          def table_structure(table_name) @@ -518,11 +543,11 @@ module ActiveRecord                       SELECT #{quoted_from_columns} FROM #{quote_table_name(from)}")          end -        def sqlite_version # :doc: +        def sqlite_version            @sqlite_version ||= SQLite3Adapter::Version.new(select_value("select sqlite_version(*)"))          end -        def translate_exception(exception, message) # :doc: +        def translate_exception(exception, message)            case exception.message            # SQLite 3.8.2 returns a newly formatted error message:            #   UNIQUE constraint failed: *table_name*.*column_name* @@ -532,6 +557,8 @@ module ActiveRecord              RecordNotUnique.new(message)            when /.* may not be NULL/, /NOT NULL constraint failed: .*/              NotNullViolation.new(message) +          when /FOREIGN KEY constraint failed/i +            InvalidForeignKey.new(message)            else              super            end @@ -581,6 +608,18 @@ module ActiveRecord          def create_table_definition(*args)            SQLite3::TableDefinition.new(*args)          end + +        def extract_foreign_key_action(specifier) +          case specifier +          when "CASCADE"; :cascade +          when "SET NULL"; :nullify +          when "RESTRICT"; :restrict +          end +        end + +        def configure_connection +          execute("PRAGMA foreign_keys = ON", "SCHEMA") +        end      end    end  end diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index d4836faa4b..0028dc0edb 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -171,23 +171,19 @@ module ActiveRecord          return super if block_given? ||                          primary_key.nil? ||                          scope_attributes? || -                        columns_hash.include?(inheritance_column) || -                        ids.first.kind_of?(Array) +                        columns_hash.include?(inheritance_column)          id = ids.first -        if ActiveRecord::Base === id -          id = id.id -          ActiveSupport::Deprecation.warn(<<-MSG.squish) -            You are passing an instance of ActiveRecord::Base to `find`. -            Please pass the id of the object by calling `.id`. -          MSG -        end + +        return super if id.kind_of?(Array) || +                         id.is_a?(ActiveRecord::Base)          key = primary_key          statement = cached_find_by_statement(key) { |params|            where(key => params.bind).limit(1)          } +          record = statement.execute([id], self, connection).first          unless record            raise RecordNotFound.new("Couldn't find #{name} with '#{primary_key}'=#{id}", @@ -200,12 +196,12 @@ module ActiveRecord        end        def find_by(*args) # :nodoc: -        return super if scope_attributes? || !(Hash === args.first) || reflect_on_all_aggregations.any? +        return super if scope_attributes? || reflect_on_all_aggregations.any?          hash = args.first -        return super if hash.values.any? { |v| -          v.nil? || Array === v || Hash === v || Relation === v +        return super if !(Hash === hash) || hash.values.any? { |v| +          v.nil? || Array === v || Hash === v || Relation === v || Base === v          }          # We can't cache Post.find_by(author: david) ...yet @@ -239,7 +235,9 @@ module ActiveRecord        def generated_association_methods          @generated_association_methods ||= begin            mod = const_set(:GeneratedAssociationMethods, Module.new) +          private_constant :GeneratedAssociationMethods            include mod +            mod          end        end @@ -310,7 +308,7 @@ module ActiveRecord            relation = Relation.create(self, arel_table, predicate_builder)            if finder_needs_type_condition? && !ignore_default_scope? -            relation.where(type_condition).create_with(inheritance_column.to_sym => sti_name) +            relation.where(type_condition).create_with(inheritance_column.to_s => sti_name)            else              relation            end @@ -452,7 +450,7 @@ module ActiveRecord      #   [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]      def hash        if id -        self.class.hash ^ self.id.hash +        self.class.hash ^ id.hash        else          super        end @@ -474,7 +472,7 @@ module ActiveRecord      # Allows sort on objects      def <=>(other_object)        if other_object.is_a?(self.class) -        self.to_key <=> other_object.to_key +        to_key <=> other_object.to_key        else          super        end diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb index e2da512813..cbd71a3779 100644 --- a/activerecord/lib/active_record/counter_cache.rb +++ b/activerecord/lib/active_record/counter_cache.rb @@ -12,13 +12,21 @@ module ActiveRecord        #        # * +id+ - The id of the object you wish to reset a counter on.        # * +counters+ - One or more association counters to reset. Association name or counter name can be given. +      # * <tt>:touch</tt> - Touch timestamp columns when updating. +      #   Pass +true+ to touch +updated_at+ and/or +updated_on+. Pass a symbol to +      #   touch that column or an array of symbols to touch just those ones.        #        # ==== Examples        # -      #   # For Post with id #1 records reset the comments_count +      #   # For the Post with id #1, reset the comments_count        #   Post.reset_counters(1, :comments) -      def reset_counters(id, *counters) +      # +      #   # Like above, but also touch the +updated_at+ and/or +updated_on+ +      #   # attributes. +      #   Post.reset_counters(1, :comments, touch: true) +      def reset_counters(id, *counters, touch: nil)          object = find(id) +          counters.each do |counter_association|            has_many_association = _reflect_on_association(counter_association)            unless has_many_association @@ -37,10 +45,12 @@ module ActiveRecord            reflection   = child_class._reflections.values.find { |e| e.belongs_to? && e.foreign_key.to_s == foreign_key && e.options[:counter_cache].present? }            counter_name = reflection.counter_cache_column -          unscoped.where(primary_key => object.id).update_all( -            counter_name => object.send(counter_association).count(:all) -          ) +          updates = { counter_name.to_sym => object.send(counter_association).count(:all) } +          updates.merge!(touch_updates(touch)) if touch + +          unscoped.where(primary_key => object.id).update_all(updates)          end +          return true        end @@ -55,6 +65,9 @@ module ActiveRecord        # * +id+ - The id of the object you wish to update a counter on or an array of ids.        # * +counters+ - A Hash containing the names of the fields        #   to update as keys and the amount to update the field by as values. +      # * <tt>:touch</tt> option - Touch timestamp columns when updating. +      #   Pass +true+ to touch +updated_at+ and/or +updated_on+. Pass a symbol to +      #   touch that column or an array of symbols to touch just those ones.        #        # ==== Examples        # @@ -73,13 +86,29 @@ module ActiveRecord        #   # UPDATE posts        #   #    SET comment_count = COALESCE(comment_count, 0) + 1        #   #  WHERE id IN (10, 15) +      # +      #   # For the Posts with id of 10 and 15, increment the comment_count by 1 +      #   # and update the updated_at value for each counter. +      #   Post.update_counters [10, 15], comment_count: 1, touch: true +      #   # Executes the following SQL: +      #   # UPDATE posts +      #   #    SET comment_count = COALESCE(comment_count, 0) + 1, +      #   #    `updated_at` = '2016-10-13T09:59:23-05:00' +      #   #  WHERE id IN (10, 15)        def update_counters(id, counters) +        touch = counters.delete(:touch) +          updates = counters.map do |counter_name, value|            operator = value < 0 ? "-" : "+"            quoted_column = connection.quote_column_name(counter_name)            "#{quoted_column} = COALESCE(#{quoted_column}, 0) #{operator} #{value.abs}"          end +        if touch +          touch_updates = touch_updates(touch) +          updates << sanitize_sql_for_assignment(touch_updates) unless touch_updates.empty? +        end +          unscoped.where(primary_key => id).update_all updates.join(", ")        end @@ -94,13 +123,20 @@ module ActiveRecord        #        # * +counter_name+ - The name of the field that should be incremented.        # * +id+ - The id of the object that should be incremented or an array of ids. +      # * <tt>:touch</tt> - Touch timestamp columns when updating. +      #   Pass +true+ to touch +updated_at+ and/or +updated_on+. Pass a symbol to +      #   touch that column or an array of symbols to touch just those ones.        #        # ==== Examples        #        #   # Increment the posts_count column for the record with an id of 5        #   DiscussionBoard.increment_counter(:posts_count, 5) -      def increment_counter(counter_name, id) -        update_counters(id, counter_name => 1) +      # +      #   # Increment the posts_count column for the record with an id of 5 +      #   # and update the updated_at value. +      #   DiscussionBoard.increment_counter(:posts_count, 5, touch: true) +      def increment_counter(counter_name, id, touch: nil) +        update_counters(id, counter_name => 1, touch: touch)        end        # Decrement a numeric field by one, via a direct SQL update. @@ -112,14 +148,28 @@ module ActiveRecord        #        # * +counter_name+ - The name of the field that should be decremented.        # * +id+ - The id of the object that should be decremented or an array of ids. +      # * <tt>:touch</tt> - Touch timestamp columns when updating. +      #   Pass +true+ to touch +updated_at+ and/or +updated_on+. Pass a symbol to +      #   touch that column or an array of symbols to touch just those ones.        #        # ==== Examples        #        #   # Decrement the posts_count column for the record with an id of 5        #   DiscussionBoard.decrement_counter(:posts_count, 5) -      def decrement_counter(counter_name, id) -        update_counters(id, counter_name => -1) +      # +      #   # Decrement the posts_count column for the record with an id of 5 +      #   # and update the updated_at value. +      #   DiscussionBoard.decrement_counter(:posts_count, 5, touch: true) +      def decrement_counter(counter_name, id, touch: nil) +        update_counters(id, counter_name => -1, touch: touch)        end + +      private +        def touch_updates(touch) +          touch = timestamp_attributes_for_update_in_model if touch == true +          touch_time = current_time_from_proper_timezone +          Array(touch).map { |column| [ column, touch_time ] }.to_h +        end      end      private diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index c812a05101..18fac5af1b 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -95,19 +95,9 @@ module ActiveRecord    #    # Wraps the underlying database error as +cause+.    class StatementInvalid < ActiveRecordError -    def initialize(message = nil, original_exception = nil) -      if original_exception -        ActiveSupport::Deprecation.warn("Passing #original_exception is deprecated and has no effect. " \ -                                        "Exceptions will automatically capture the original exception.", caller) -      end - +    def initialize(message = nil)        super(message || $!.try(:message))      end - -    def original_exception -      ActiveSupport::Deprecation.warn("#original_exception is deprecated. Use #cause instead.", caller) -      cause -    end    end    # Defunct wrapper class kept for compatibility. diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 3b4532a3f2..91d8054ef2 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -862,29 +862,17 @@ module ActiveRecord        class_attribute :fixture_table_names        class_attribute :fixture_class_names        class_attribute :use_transactional_tests -      class_attribute :use_transactional_fixtures        class_attribute :use_instantiated_fixtures # true, false, or :no_instances        class_attribute :pre_loaded_fixtures        class_attribute :config -      singleton_class.deprecate "use_transactional_fixtures=" => "use use_transactional_tests= instead" -        self.fixture_table_names = []        self.use_instantiated_fixtures = false        self.pre_loaded_fixtures = false        self.config = ActiveRecord::Base        self.fixture_class_names = {} - -      silence_warnings do -        define_singleton_method :use_transactional_tests do -          if use_transactional_fixtures.nil? -            true -          else -            use_transactional_fixtures -          end -        end -      end +      self.use_transactional_tests = true      end      module ClassMethods @@ -897,7 +885,7 @@ module ActiveRecord        #        # The keys must be the fixture names, that coincide with the short paths to the fixture files.        def set_fixture_class(class_names = {}) -        self.fixture_class_names = self.fixture_class_names.merge(class_names.stringify_keys) +        self.fixture_class_names = fixture_class_names.merge(class_names.stringify_keys)        end        def fixtures(*fixture_set_names) diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb index a1d4f47372..fbdaeaae51 100644 --- a/activerecord/lib/active_record/inheritance.rb +++ b/activerecord/lib/active_record/inheritance.rb @@ -130,16 +130,26 @@ module ActiveRecord          store_full_sti_class ? name : name.demodulize        end +      def inherited(subclass) +        subclass.instance_variable_set(:@_type_candidates_cache, Concurrent::Map.new) +        super +      end +        protected          # Returns the class type of the record using the current module as a prefix. So descendants of          # MyApp::Business::Account would appear as MyApp::Business::AccountSubclass.          def compute_type(type_name) -          if type_name.match(/^::/) +          if type_name.start_with?("::".freeze)              # If the type is prefixed with a scope operator then we assume that              # the type_name is an absolute reference.              ActiveSupport::Dependencies.constantize(type_name)            else +            type_candidate = @_type_candidates_cache[type_name] +            if type_candidate && type_constant = ActiveSupport::Dependencies.safe_constantize(type_candidate) +              return type_constant +            end +              # Build a list of candidates to search for              candidates = []              name.scan(/::|$/) { candidates.unshift "#{$`}::#{type_name}" } @@ -147,7 +157,10 @@ module ActiveRecord              candidates.each do |candidate|                constant = ActiveSupport::Dependencies.safe_constantize(candidate) -              return constant if candidate == constant.to_s +              if candidate == constant.to_s +                @_type_candidates_cache[type_name] = candidate +                return constant +              end              end              raise NameError.new("uninitialized constant #{candidates.first}", candidates.first) diff --git a/activerecord/lib/active_record/internal_metadata.rb b/activerecord/lib/active_record/internal_metadata.rb index 20d61dba67..25ee9d6bfe 100644 --- a/activerecord/lib/active_record/internal_metadata.rb +++ b/activerecord/lib/active_record/internal_metadata.rb @@ -23,7 +23,7 @@ module ActiveRecord        end        def table_exists? -        ActiveSupport::Deprecation.silence { connection.table_exists?(table_name) } +        connection.table_exists?(table_name)        end        # Creates an internal metadata table with columns +key+ and +value+ diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb index 4b8d8d9105..ea101946f4 100644 --- a/activerecord/lib/active_record/log_subscriber.rb +++ b/activerecord/lib/active_record/log_subscriber.rb @@ -81,7 +81,7 @@ module ActiveRecord            RED          when /transaction\s*\Z/i            CYAN -          else +        else            MAGENTA          end        end diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index cc6bc17b9d..40f6226315 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -522,7 +522,10 @@ module ActiveRecord      def self.inherited(subclass) # :nodoc:        super        if subclass.superclass == Migration -        subclass.include Compatibility::Legacy +        raise StandardError, "Directly inheriting from ActiveRecord::Migration is not supported. " \ +          "Please specify the Rails release the migration was written for:\n" \ +          "\n" \ +          "  class #{subclass} < ActiveRecord::Migration[4.2]"        end      end @@ -689,7 +692,7 @@ module ActiveRecord        connection.respond_to?(:reverting) && connection.reverting      end -    class ReversibleBlockHelper < Struct.new(:reverting) # :nodoc: +    ReversibleBlockHelper = Struct.new(:reverting) do # :nodoc:        def up          yield unless reverting        end @@ -935,7 +938,7 @@ module ActiveRecord    # MigrationProxy is used to defer loading of the actual migration classes    # until they are needed -  class MigrationProxy < Struct.new(:name, :version, :filename, :scope) +  MigrationProxy = Struct.new(:name, :version, :filename, :scope) do      def initialize(name, version, filename, scope)        super        @migration = nil @@ -1026,12 +1029,10 @@ module ActiveRecord        end        def get_all_versions(connection = Base.connection) -        ActiveSupport::Deprecation.silence do -          if connection.table_exists?(schema_migrations_table_name) -            SchemaMigration.all.map { |x| x.version.to_i }.sort -          else -            [] -          end +        if connection.table_exists?(schema_migrations_table_name) +          SchemaMigration.all.map { |x| x.version.to_i }.sort +        else +          []          end        end @@ -1106,8 +1107,8 @@ module ActiveRecord        validate(@migrations) -      Base.connection.initialize_schema_migrations_table -      Base.connection.initialize_internal_metadata_table +      ActiveRecord::SchemaMigration.create_table +      ActiveRecord::InternalMetadata.create_table      end      def current_version @@ -1169,9 +1170,10 @@ module ActiveRecord        def run_without_lock          migration = migrations.detect { |m| m.version == @target_version }          raise UnknownMigrationVersionError.new(@target_version) if migration.nil? -        execute_migration_in_transaction(migration, @direction) +        result = execute_migration_in_transaction(migration, @direction)          record_environment +        result        end        # Used for running multiple migrations up to or down to a certain value. @@ -1180,11 +1182,12 @@ module ActiveRecord            raise UnknownMigrationVersionError.new(@target_version)          end -        runnable.each do |migration| +        result = runnable.each do |migration|            execute_migration_in_transaction(migration, @direction)          end          record_environment +        result        end        # Stores the current environment in the database. diff --git a/activerecord/lib/active_record/migration/compatibility.rb b/activerecord/lib/active_record/migration/compatibility.rb index 9c357e1604..f6c9127d34 100644 --- a/activerecord/lib/active_record/migration/compatibility.rb +++ b/activerecord/lib/active_record/migration/compatibility.rb @@ -13,7 +13,32 @@ module ActiveRecord        V5_1 = Current -      module FourTwoShared +      class V5_0 < V5_1 +        def create_table(table_name, options = {}) +          if adapter_name == "PostgreSQL" +            if options[:id] == :uuid && !options.key?(:default) +              options[:default] = "uuid_generate_v4()" +            end +          end + +          unless adapter_name == "Mysql2" && options[:id] == :bigint +            if [:integer, :bigint].include?(options[:id]) && !options.key?(:default) +              options[:default] = nil +            end +          end + +          # Since 5.1 Postgres adapter uses bigserial type for primary +          # keys by default and MySQL uses bigint. This compat layer makes old migrations utilize +          # serial/int type instead -- the way it used to work before 5.1. +          unless options.key?(:id) +            options[:id] = :integer +          end + +          super +        end +      end + +      class V4_2 < V5_0          module TableDefinition            def references(*, **options)              options[:index] ||= false @@ -101,46 +126,6 @@ module ActiveRecord              index_name            end        end - -      class V5_0 < V5_1 -        def create_table(table_name, options = {}) -          if adapter_name == "PostgreSQL" -            if options[:id] == :uuid && !options[:default] -              options[:default] = "uuid_generate_v4()" -            end -          end - -          # Since 5.1 Postgres adapter uses bigserial type for primary -          # keys by default and MySQL uses bigint. This compat layer makes old migrations utilize -          # serial/int type instead -- the way it used to work before 5.1. -          if options[:id].blank? -            options[:id] = :integer -            options[:auto_increment] = true -          end - -          super -        end -      end - -      class V4_2 < V5_0 -        # 4.2 is defined as a module because it needs to be shared with -        # Legacy. When the time comes, V5_0 should be defined straight -        # in its class. -        include FourTwoShared -      end - -      module Legacy -        include FourTwoShared - -        def migrate(*) -          ActiveSupport::Deprecation.warn \ -            "Directly inheriting from ActiveRecord::Migration is deprecated. " \ -            "Please specify the Rails release the migration was written for:\n" \ -            "\n" \ -            "  class #{self.class.name} < ActiveRecord::Migration[4.2]" -          super -        end -      end      end    end  end diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index e983026961..01ecd79b8f 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -393,7 +393,7 @@ module ActiveRecord        # update_only is true, and a <tt>:_destroy</tt> key set to a truthy value,        # then the existing record will be marked for destruction.        def assign_nested_attributes_for_one_to_one_association(association_name, attributes) -        options = self.nested_attributes_options[association_name] +        options = nested_attributes_options[association_name]          if attributes.respond_to?(:permitted?)            attributes = attributes.to_h          end @@ -452,7 +452,7 @@ module ActiveRecord        #     { id: '2', _destroy: true }        #   ])        def assign_nested_attributes_for_collection_association(association_name, attributes_collection) -        options = self.nested_attributes_options[association_name] +        options = nested_attributes_options[association_name]          if attributes_collection.respond_to?(:permitted?)            attributes_collection = attributes_collection.to_h          end @@ -562,7 +562,7 @@ module ActiveRecord        def call_reject_if(association_name, attributes)          return false if will_be_destroyed?(association_name, attributes) -        case callback = self.nested_attributes_options[association_name][:reject_if] +        case callback = nested_attributes_options[association_name][:reject_if]          when Symbol            method(callback).arity == 0 ? send(callback) : send(callback, attributes)          when Proc diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 60d8e95b21..7ceb7d1a55 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -148,6 +148,8 @@ module ActiveRecord      #      # Attributes marked as readonly are silently ignored if the record is      # being updated. +    # +    # Unless an error is raised, returns true.      def save!(*args)        create_or_update(*args) || raise(RecordNotSaved.new("Failed to save the record", self))      end @@ -338,14 +340,16 @@ module ActiveRecord        self      end -    # Wrapper around #increment that saves the record. This method differs from -    # its non-bang version in that it passes through the attribute setter. -    # Saving is not subjected to validation checks. Returns +true+ if the -    # record could be saved. -    def increment!(attribute, by = 1) +    # Wrapper around #increment that writes the update to the database. +    # Only +attribute+ is updated; the record itself is not saved. +    # This means that any other modified attributes will still be dirty. +    # Validations and callbacks are skipped. Supports the `touch` option from +    # +update_counters+, see that for more. +    # Returns +self+. +    def increment!(attribute, by = 1, touch: nil)        increment(attribute, by)        change = public_send(attribute) - (attribute_in_database(attribute.to_s) || 0) -      self.class.update_counters(id, attribute => change) +      self.class.update_counters(id, attribute => change, touch: touch)        clear_attribute_change(attribute) # eww        self      end @@ -357,12 +361,14 @@ module ActiveRecord        increment(attribute, -by)      end -    # Wrapper around #decrement that saves the record. This method differs from -    # its non-bang version in the sense that it passes through the attribute setter. -    # Saving is not subjected to validation checks. Returns +true+ if the -    # record could be saved. -    def decrement!(attribute, by = 1) -      increment!(attribute, -by) +    # Wrapper around #decrement that writes the update to the database. +    # Only +attribute+ is updated; the record itself is not saved. +    # This means that any other modified attributes will still be dirty. +    # Validations and callbacks are skipped. Supports the `touch` option from +    # +update_counters+, see that for more. +    # Returns +self+. +    def decrement!(attribute, by = 1, touch: nil) +      increment!(attribute, -by, touch: touch)      end      # Assigns to +attribute+ the boolean opposite of <tt>attribute?</tt>. So @@ -392,8 +398,8 @@ module ActiveRecord      # Reloads the record from the database.      # -    # This method finds record by its primary key (which could be assigned manually) and -    # modifies the receiver in-place: +    # This method finds the record by its primary key (which could be assigned +    # manually) and modifies the receiver in-place:      #      #   account = Account.new      #   # => #<Account id: nil, email: nil> diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index 2701c5bca9..0276d41494 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -87,8 +87,8 @@ module ActiveRecord              if File.file?(filename)                cache = YAML.load(File.read(filename))                if cache.version == ActiveRecord::Migrator.current_version -                self.connection.schema_cache = cache -                self.connection_pool.schema_cache = cache.dup +                connection.schema_cache = cache +                connection_pool.schema_cache = cache.dup                else                  warn "Ignoring db/schema_cache.yml because it has expired. The current schema version is #{ActiveRecord::Migrator.current_version}, but the one in the cache is #{cache.version}."                end diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 25d79a6c7d..246d330b76 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -269,10 +269,7 @@ db_namespace = namespace :db do        task dump: [:environment, :load_config] do          conn = ActiveRecord::Base.connection          filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.yml") - -        conn.schema_cache.clear! -        conn.data_sources.each { |table| conn.schema_cache.add(table) } -        open(filename, "wb") { |f| f.write(YAML.dump(conn.schema_cache)) } +        ActiveRecord::Tasks::DatabaseTasks.dump_schema_cache(conn, filename)        end        desc "Clears a db/schema_cache.yml file." @@ -312,14 +309,6 @@ db_namespace = namespace :db do    end    namespace :test do - -    task :deprecated do -      Rake.application.top_level_tasks.grep(/^db:test:/).each do |task| -        $stderr.puts "WARNING: #{task} is deprecated. The Rails test helper now maintains " \ -                     "your test schema automatically, see the release notes for details." -      end -    end -      # desc "Recreate the test database from the current schema"      task load: %w(db:test:purge) do        case ActiveRecord::Base.schema_format @@ -348,22 +337,6 @@ db_namespace = namespace :db do        ActiveRecord::Tasks::DatabaseTasks.load_schema ActiveRecord::Base.configurations["test"], :sql, ENV["SCHEMA"]      end -    # desc "Recreate the test database from a fresh schema" -    task clone: %w(db:test:deprecated environment) do -      case ActiveRecord::Base.schema_format -      when :ruby -        db_namespace["test:clone_schema"].invoke -      when :sql -        db_namespace["test:clone_structure"].invoke -      end -    end - -    # desc "Recreate the test database from a fresh schema.rb file" -    task clone_schema: %w(db:test:deprecated db:schema:dump db:test:load_schema) - -    # desc "Recreate the test database from a fresh structure.sql file" -    task clone_structure: %w(db:test:deprecated db:structure:dump db:test:load_structure) -      # desc "Empty the test database"      task purge: %w(environment load_config check_protected_environments) do        ActiveRecord::Tasks::DatabaseTasks.purge ActiveRecord::Base.configurations["test"] diff --git a/activerecord/lib/active_record/readonly_attributes.rb b/activerecord/lib/active_record/readonly_attributes.rb index 8ff265bdfa..6274996ab8 100644 --- a/activerecord/lib/active_record/readonly_attributes.rb +++ b/activerecord/lib/active_record/readonly_attributes.rb @@ -11,7 +11,7 @@ module ActiveRecord        # Attributes listed as readonly will be used to create a new record but update operations will        # ignore these fields.        def attr_readonly(*attributes) -        self._attr_readonly = Set.new(attributes.map(&:to_s)) + (self._attr_readonly || []) +        self._attr_readonly = Set.new(attributes.map(&:to_s)) + (_attr_readonly || [])        end        # Returns an array of all the attributes that have been specified as readonly. diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index e1a3c59f08..61a2279292 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -1,5 +1,6 @@  require "thread"  require "active_support/core_ext/string/filters" +require "active_support/deprecation"  module ActiveRecord    # = Active Record Reflection @@ -175,8 +176,19 @@ module ActiveRecord          JoinKeys.new(foreign_key, active_record_primary_key)        end +      # Returns a list of scopes that should be applied for this Reflection +      # object when querying the database. +      def scopes +        scope ? [scope] : [] +      end + +      def scope_chain +        chain.map(&:scopes) +      end +      deprecate :scope_chain +        def constraints -        scope_chain.flatten +        chain.map(&:scopes).flatten        end        def counter_cache_column @@ -321,7 +333,7 @@ module ActiveRecord          end      end -    # Holds all the meta-data about an aggregation as it was specified in the +    # Holds all the metadata about an aggregation as it was specified in the      # Active Record class.      class AggregateReflection < MacroReflection #:nodoc:        def mapping @@ -330,7 +342,7 @@ module ActiveRecord        end      end -    # Holds all the meta-data about an association as it was specified in the +    # Holds all the metadata about an association as it was specified in the      # Active Record class.      class AssociationReflection < MacroReflection #:nodoc:        # Returns the target association's class. @@ -364,6 +376,17 @@ module ActiveRecord          @constructable = calculate_constructable(macro, options)          @association_scope_cache = {}          @scope_lock = Mutex.new + +        if options[:class_name] && options[:class_name].class == Class +          ActiveSupport::Deprecation.warn(<<-MSG.squish) +            Passing a class to the `class_name` is deprecated and will raise +            an ArgumentError in Rails 5.2. It eagerloads more classes than +            necessary and potentially creates circular dependencies. + +            Please pass the class name as a string: +            `#{macro} :#{name}, class_name: '#{options[:class_name]}'` +          MSG +        end        end        def association_scope_cache(conn, owner) @@ -398,7 +421,7 @@ module ActiveRecord        end        def association_primary_key_type -        klass.type_for_attribute(association_primary_key) +        klass.type_for_attribute(association_primary_key.to_s)        end        def active_record_primary_key @@ -450,12 +473,6 @@ module ActiveRecord          false        end -      # An array of arrays of scopes. Each item in the outside array corresponds to a reflection -      # in the #chain. -      def scope_chain -        scope ? [[scope]] : [[]] -      end -        def has_scope?          scope        end @@ -698,7 +715,7 @@ module ActiveRecord        end      end -    # Holds all the meta-data about a :through association as it was specified +    # Holds all the metadata about a :through association as it was specified      # in the Active Record class.      class ThroughReflection < AbstractReflection #:nodoc:        attr_reader :delegate_reflection @@ -785,45 +802,12 @@ module ActiveRecord          through_reflection.clear_association_scope_cache        end -      # Consider the following example: -      # -      #   class Person -      #     has_many :articles -      #     has_many :comment_tags, through: :articles -      #   end -      # -      #   class Article -      #     has_many :comments -      #     has_many :comment_tags, through: :comments, source: :tags -      #   end -      # -      #   class Comment -      #     has_many :tags -      #   end -      # -      # There may be scopes on Person.comment_tags, Article.comment_tags and/or Comment.tags, -      # but only Comment.tags will be represented in the #chain. So this method creates an array -      # of scopes corresponding to the chain. -      def scope_chain -        @scope_chain ||= begin -          scope_chain = source_reflection.scope_chain.map(&:dup) - -          # Add to it the scope from this reflection (if any) -          scope_chain.first << scope if scope - -          through_scope_chain = through_reflection.scope_chain.map(&:dup) - -          if options[:source_type] -            type = foreign_type -            source_type = options[:source_type] -            through_scope_chain.first << lambda { |object| -              where(type => source_type) -            } -          end +      def scopes +        source_reflection.scopes + super +      end -          # Recursively fill out the rest of the array from the through reflection -          scope_chain + through_scope_chain -        end +      def source_type_scope +        through_reflection.klass.where(foreign_type => options[:source_type])        end        def has_scope? @@ -851,7 +835,7 @@ module ActiveRecord        end        def association_primary_key_type -        klass.type_for_attribute(association_primary_key) +        klass.type_for_attribute(association_primary_key.to_s)        end        # Gets an array of possible <tt>:through</tt> source reflection names in both singular and plural form. @@ -878,15 +862,13 @@ module ActiveRecord          }          if names.length > 1 -          example_options = options.dup -          example_options[:source] = source_reflection_names.first -          ActiveSupport::Deprecation.warn \ -            "Ambiguous source reflection for through association. Please " \ -            "specify a :source directive on your declaration like:\n" \ -            "\n" \ -            "  class #{active_record.name} < ActiveRecord::Base\n" \ -            "    #{macro} :#{name}, #{example_options}\n" \ -            "  end" +          raise AmbiguousSourceReflectionForThroughAssociation.new( +            active_record.name, +            macro, +            name, +            options, +            source_reflection_names +          )          end          @source_reflection_name = names.first @@ -933,6 +915,14 @@ module ActiveRecord            raise HasOneThroughCantAssociateThroughCollection.new(active_record.name, self, through_reflection)          end +        if parent_reflection.nil? +          reflections = active_record.reflections.keys.map(&:to_sym) + +          if reflections.index(through_reflection.name) > reflections.index(name) +            raise HasManyThroughOrderError.new(active_record.name, self, through_reflection) +          end +        end +          check_validity_of_inverse!        end @@ -963,8 +953,7 @@ module ActiveRecord          end        end -      protected - +      private          def actual_source_reflection # FIXME: this is a horrible name            source_reflection.send(:actual_source_reflection)          end @@ -975,7 +964,6 @@ module ActiveRecord          def inverse_name; delegate_reflection.send(:inverse_name); end -      private          def derive_class_name            # get the class_name of the belongs_to association of the through reflection            options[:source_type] || source_reflection.class_name @@ -993,6 +981,15 @@ module ActiveRecord          @previous_reflection = previous_reflection        end +      def scopes +        scopes = @previous_reflection.scopes +        if @previous_reflection.options[:source_type] +          scopes + [@previous_reflection.source_type_scope] +        else +          scopes +        end +      end +        def klass          @reflection.klass        end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 4e941cf2df..61ee09bcc8 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -418,8 +418,7 @@ module ActiveRecord          records.each { |record| record.update(attributes) }        else          if ActiveRecord::Base === id -          id = id.id -          ActiveSupport::Deprecation.warn(<<-MSG.squish) +          raise ArgumentError, <<-MSG.squish              You are passing an instance of ActiveRecord::Base to `update`.              Please pass the id of the object by calling `.id`.            MSG @@ -446,16 +445,8 @@ module ActiveRecord      # ==== Examples      #      #   Person.where(age: 0..18).destroy_all -    def destroy_all(conditions = nil) -      if conditions -        ActiveSupport::Deprecation.warn(<<-MESSAGE.squish) -          Passing conditions to destroy_all is deprecated and will be removed in Rails 5.1. -          To achieve the same use where(conditions).destroy_all. -        MESSAGE -        where(conditions).destroy_all -      else -        records.each(&:destroy).tap { reset } -      end +    def destroy_all +      records.each(&:destroy).tap { reset }      end      # Destroy an object (or multiple objects) that has the given id. The object is instantiated first, @@ -503,7 +494,7 @@ module ActiveRecord      #      #   Post.limit(100).delete_all      #   # => ActiveRecord::ActiveRecordError: delete_all doesn't support limit -    def delete_all(conditions = nil) +    def delete_all        invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select do |method|          value = get_value(method)          SINGLE_VALUE_METHODS.include?(method) ? value : value.any? @@ -512,27 +503,19 @@ module ActiveRecord          raise ActiveRecordError.new("delete_all doesn't support #{invalid_methods.join(', ')}")        end -      if conditions -        ActiveSupport::Deprecation.warn(<<-MESSAGE.squish) -          Passing conditions to delete_all is deprecated and will be removed in Rails 5.1. -          To achieve the same use where(conditions).delete_all. -        MESSAGE -        where(conditions).delete_all -      else -        stmt = Arel::DeleteManager.new -        stmt.from(table) +      stmt = Arel::DeleteManager.new +      stmt.from(table) -        if has_join_values? -          @klass.connection.join_to_delete(stmt, arel, arel_attribute(primary_key)) -        else -          stmt.wheres = arel.constraints -        end +      if has_join_values? +        @klass.connection.join_to_delete(stmt, arel, arel_attribute(primary_key)) +      else +        stmt.wheres = arel.constraints +      end -        affected = @klass.connection.delete(stmt, "SQL", bound_attributes) +      affected = @klass.connection.delete(stmt, "SQL", bound_attributes) -        reset -        affected -      end +      reset +      affected      end      # Deletes the row with a primary key matching the +id+ argument, using a @@ -630,15 +613,6 @@ module ActiveRecord        includes_values & joins_values      end -    # {#uniq}[rdoc-ref:QueryMethods#uniq] and -    # {#uniq!}[rdoc-ref:QueryMethods#uniq!] are silently deprecated. -    # #uniq_value delegates to #distinct_value to maintain backwards compatibility. -    # Use #distinct_value instead. -    def uniq_value -      distinct_value -    end -    deprecate uniq_value: :distinct_value -      # Compares two relations for equality.      def ==(other)        case other diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb index 4b2987ac6d..76031515fd 100644 --- a/activerecord/lib/active_record/relation/batches.rb +++ b/activerecord/lib/active_record/relation/batches.rb @@ -260,7 +260,7 @@ module ActiveRecord        end        def act_on_ignored_order(error_on_ignore) -        raise_error = (error_on_ignore.nil? ? self.klass.error_on_ignored_order : error_on_ignore) +        raise_error = (error_on_ignore.nil? ? klass.error_on_ignored_order : error_on_ignore)          if raise_error            raise ArgumentError.new(ORDER_IGNORE_MESSAGE) diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 827688a663..e8aba02611 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -193,7 +193,7 @@ module ActiveRecord          # If #count is used with #distinct (i.e. `relation.distinct.count`) it is          # considered distinct. -        distinct = self.distinct_value +        distinct = distinct_value          if operation == "count"            column_name ||= select_for_count @@ -364,15 +364,14 @@ module ActiveRecord        end        def build_count_subquery(relation, column_name, distinct) -        column_alias = Arel.sql("count_column")          subquery_alias = Arel.sql("subquery_for_count") -        aliased_column = aggregate_column(column_name == :all ? 1 : column_name).as(column_alias) +        aliased_column = aggregate_column(column_name == :all ? "1" : column_name)          relation.select_values = [aliased_column]          subquery = relation.arel.as(subquery_alias)          sm = Arel::SelectManager.new relation.engine -        select_value = operation_over_aggregate_column(column_alias, "count", distinct) +        select_value = operation_over_aggregate_column(Arel.star, "count", distinct)          sm.project(select_value).from(subquery)        end    end diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb index 43dac0ed3d..f965818ed2 100644 --- a/activerecord/lib/active_record/relation/delegation.rb +++ b/activerecord/lib/active_record/relation/delegation.rb @@ -15,7 +15,10 @@ module ActiveRecord            delegate = Class.new(klass) {              include ClassSpecificRelation            } -          const_set klass.name.gsub("::".freeze, "_".freeze), delegate +          mangled_name = klass.name.gsub("::".freeze, "_".freeze) +          const_set mangled_name, delegate +          private_constant mangled_name +            cache[klass] = delegate          end        end @@ -110,7 +113,7 @@ module ActiveRecord          arel.respond_to?(method, include_private)      end -    protected +    private        def method_missing(method, *args, &block)          if @klass.respond_to?(method) diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index d74f15d479..4548944fe6 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -17,8 +17,8 @@ module ActiveRecord      #   Person.where("administrator = 1").order("created_on DESC").find(1)      #      # NOTE: The returned records may not be in the same order as the ids you -    # provide since database rows are unordered. You'd need to provide an explicit QueryMethods#order -    # option if you want the results are sorted. +    # provide since database rows are unordered. You will need to provide an explicit QueryMethods#order +    # option if you want the results to be sorted.      #      # ==== Find with lock      # @@ -152,14 +152,6 @@ module ActiveRecord        result = result.reverse_order!        limit ? result.reverse : result.first -    rescue ActiveRecord::IrreversibleOrderError -      ActiveSupport::Deprecation.warn(<<-WARNING.squish) -          Finding a last element by loading the relation when SQL ORDER -          can not be reversed is deprecated. -          Rails 5.1 will raise ActiveRecord::IrreversibleOrderError in this case. -          Please call `to_a.last` if you still want to load the relation. -      WARNING -      find_last(limit)      end      # Same as #last but raises ActiveRecord::RecordNotFound if no record @@ -309,8 +301,7 @@ module ActiveRecord      #   Person.exists?      def exists?(conditions = :none)        if Base === conditions -        conditions = conditions.id -        ActiveSupport::Deprecation.warn(<<-MSG.squish) +        raise ArgumentError, <<-MSG.squish            You are passing an instance of ActiveRecord::Base to `exists?`.            Please pass the id of the object by calling `.id`.          MSG @@ -441,139 +432,138 @@ module ActiveRecord        private -      def find_with_ids(*ids) # :doc: -        raise UnknownPrimaryKey.new(@klass) if primary_key.nil? +        def find_with_ids(*ids) +          raise UnknownPrimaryKey.new(@klass) if primary_key.nil? -        expects_array = ids.first.kind_of?(Array) -        return ids.first if expects_array && ids.first.empty? +          expects_array = ids.first.kind_of?(Array) +          return ids.first if expects_array && ids.first.empty? -        ids = ids.flatten.compact.uniq +          ids = ids.flatten.compact.uniq -        case ids.size -        when 0 -          raise RecordNotFound, "Couldn't find #{@klass.name} without an ID" -        when 1 -          result = find_one(ids.first) -          expects_array ? [ result ] : result -        else -          find_some(ids) +          case ids.size +          when 0 +            raise RecordNotFound, "Couldn't find #{@klass.name} without an ID" +          when 1 +            result = find_one(ids.first) +            expects_array ? [ result ] : result +          else +            find_some(ids) +          end +        rescue ::RangeError +          raise RecordNotFound, "Couldn't find #{@klass.name} with an out of range ID"          end -      rescue ::RangeError -        raise RecordNotFound, "Couldn't find #{@klass.name} with an out of range ID" -      end -      def find_one(id) # :doc: -        if ActiveRecord::Base === id -          id = id.id -          ActiveSupport::Deprecation.warn(<<-MSG.squish) -            You are passing an instance of ActiveRecord::Base to `find`. -            Please pass the id of the object by calling `.id`. -          MSG -        end +        def find_one(id) +          if ActiveRecord::Base === id +            raise ArgumentError, <<-MSG.squish +              You are passing an instance of ActiveRecord::Base to `find`. +              Please pass the id of the object by calling `.id`. +            MSG +          end -        relation = where(primary_key => id) -        record = relation.take +          relation = where(primary_key => id) +          record = relation.take -        raise_record_not_found_exception!(id, 0, 1) unless record +          raise_record_not_found_exception!(id, 0, 1) unless record -        record -      end +          record +        end -      def find_some(ids) # :doc: -        return find_some_ordered(ids) unless order_values.present? +        def find_some(ids) +          return find_some_ordered(ids) unless order_values.present? -        result = where(primary_key => ids).to_a +          result = where(primary_key => ids).to_a -        expected_size = -          if limit_value && ids.size > limit_value -            limit_value -          else -            ids.size -          end +          expected_size = +            if limit_value && ids.size > limit_value +              limit_value +            else +              ids.size +            end -        # 11 ids with limit 3, offset 9 should give 2 results. -        if offset_value && (ids.size - offset_value < expected_size) -          expected_size = ids.size - offset_value -        end +          # 11 ids with limit 3, offset 9 should give 2 results. +          if offset_value && (ids.size - offset_value < expected_size) +            expected_size = ids.size - offset_value +          end -        if result.size == expected_size -          result -        else -          raise_record_not_found_exception!(ids, result.size, expected_size) +          if result.size == expected_size +            result +          else +            raise_record_not_found_exception!(ids, result.size, expected_size) +          end          end -      end -      def find_some_ordered(ids) # :doc: -        ids = ids.slice(offset_value || 0, limit_value || ids.size) || [] +        def find_some_ordered(ids) +          ids = ids.slice(offset_value || 0, limit_value || ids.size) || [] -        result = except(:limit, :offset).where(primary_key => ids).records +          result = except(:limit, :offset).where(primary_key => ids).records -        if result.size == ids.size -          pk_type = @klass.type_for_attribute(primary_key) +          if result.size == ids.size +            pk_type = @klass.type_for_attribute(primary_key) -          records_by_id = result.index_by(&:id) -          ids.map { |id| records_by_id.fetch(pk_type.cast(id)) } -        else -          raise_record_not_found_exception!(ids, result.size, ids.size) +            records_by_id = result.index_by(&:id) +            ids.map { |id| records_by_id.fetch(pk_type.cast(id)) } +          else +            raise_record_not_found_exception!(ids, result.size, ids.size) +          end          end -      end -      def find_take # :doc: -        if loaded? -          records.first -        else -          @take ||= limit(1).records.first +        def find_take +          if loaded? +            records.first +          else +            @take ||= limit(1).records.first +          end          end -      end -      def find_take_with_limit(limit) # :doc: -        if loaded? -          records.take(limit) -        else -          limit(limit).to_a +        def find_take_with_limit(limit) +          if loaded? +            records.take(limit) +          else +            limit(limit).to_a +          end          end -      end -      def find_nth(index) # :doc: -        @offsets[offset_index + index] ||= find_nth_with_limit(index, 1).first -      end +        def find_nth(index) +          @offsets[offset_index + index] ||= find_nth_with_limit(index, 1).first +        end -      def find_nth_with_limit(index, limit) # :doc: -        if loaded? -          records[index, limit] || [] -        else -          relation = if order_values.empty? && primary_key -            order(arel_attribute(primary_key).asc) +        def find_nth_with_limit(index, limit) +          if loaded? +            records[index, limit] || []            else -            self +            relation = if order_values.empty? && primary_key +              order(arel_attribute(primary_key).asc) +            else +              self +            end + +            relation = relation.offset(offset_index + index) unless index.zero? +            relation.limit(limit).to_a            end - -          relation = relation.offset(offset_index + index) unless index.zero? -          relation.limit(limit).to_a          end -      end -      def find_nth_from_last(index) # :doc: -        if loaded? -          records[-index] -        else -          relation = if order_values.empty? && primary_key -            order(arel_attribute(primary_key).asc) +        def find_nth_from_last(index) +          if loaded? +            records[-index]            else -            self +            relation = if order_values.empty? && primary_key +              order(arel_attribute(primary_key).asc) +            else +              self +            end + +            relation.to_a[-index] +            # TODO: can be made more performant on large result sets by +            # for instance, last(index)[-index] (which would require +            # refactoring the last(n) finder method to make test suite pass), +            # or by using a combination of reverse_order, limit, and offset, +            # e.g., reverse_order.offset(index-1).first            end - -          relation.to_a[-index] -          # TODO: can be made more performant on large result sets by -          # for instance, last(index)[-index] (which would require -          # refactoring the last(n) finder method to make test suite pass), -          # or by using a combination of reverse_order, limit, and offset, -          # e.g., reverse_order.offset(index-1).first          end -      end -      def find_last(limit) -        limit ? records.last(limit) : records.last -      end +        def find_last(limit) +          limit ? records.last(limit) : records.last +        end    end  end diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index f9f6ff403e..18ae10a652 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -4,7 +4,6 @@ module ActiveRecord      require "active_record/relation/predicate_builder/association_query_handler"      require "active_record/relation/predicate_builder/base_handler"      require "active_record/relation/predicate_builder/basic_object_handler" -    require "active_record/relation/predicate_builder/class_handler"      require "active_record/relation/predicate_builder/polymorphic_array_handler"      require "active_record/relation/predicate_builder/range_handler"      require "active_record/relation/predicate_builder/relation_handler" @@ -16,7 +15,6 @@ module ActiveRecord        @handlers = []        register_handler(BasicObject, BasicObjectHandler.new) -      register_handler(Class, ClassHandler.new(self))        register_handler(Base, BaseHandler.new(self))        register_handler(Range, RangeHandler.new)        register_handler(RangeHandler::RangeWithBinds, RangeHandler.new) diff --git a/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb index dfffbbd14b..29860ec677 100644 --- a/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb +++ b/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb @@ -70,9 +70,6 @@ module ActiveRecord            case value            when Relation              value.klass.base_class -          when Array -            val = value.compact.first -            val.class.base_class if val.is_a?(Base)            when Base              value.class.base_class            end diff --git a/activerecord/lib/active_record/relation/predicate_builder/class_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/class_handler.rb deleted file mode 100644 index 810937ead6..0000000000 --- a/activerecord/lib/active_record/relation/predicate_builder/class_handler.rb +++ /dev/null @@ -1,29 +0,0 @@ -module ActiveRecord -  class PredicateBuilder -    class ClassHandler # :nodoc: -      def initialize(predicate_builder) -        @predicate_builder = predicate_builder -      end - -      def call(attribute, value) -        print_deprecation_warning -        predicate_builder.build(attribute, value.name) -      end - -      # TODO Change this to private once we've dropped Ruby 2.2 support. -      # Workaround for Ruby 2.2 "private attribute?" warning. -      protected - -        attr_reader :predicate_builder - -      private - -        def print_deprecation_warning -          ActiveSupport::Deprecation.warn(<<-MSG.squish) -            Passing a class as a value in an Active Record query is deprecated and -            will be removed. Pass a string instead. -          MSG -        end -    end -  end -end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 5f5d8ceea3..4ee413c805 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -3,7 +3,6 @@ require "active_record/relation/query_attribute"  require "active_record/relation/where_clause"  require "active_record/relation/where_clause_factory"  require "active_model/forbidden_attributes_protection" -require "active_support/core_ext/string/filters"  module ActiveRecord    module QueryMethods @@ -76,7 +75,7 @@ module ActiveRecord      end      def bound_attributes -      if limit_value && !string_containing_comma?(limit_value) +      if limit_value          limit_bind = Attribute.with_cast_value(            "LIMIT".freeze,            connection.sanitize_limit(limit_value), @@ -243,9 +242,7 @@ module ActiveRecord      def select(*fields)        if block_given?          if fields.any? -          ActiveSupport::Deprecation.warn(<<-WARNING.squish) -            When select is called with a block, it ignores other arguments. This behavior is now deprecated and will result in an ArgumentError in Rails 5.1. You can safely remove the arguments to resolve the deprecation warning because they do not have any effect on the output of the call to the select method with a block. -          WARNING +          raise ArgumentError, "`select' with block doesn't take arguments."          end          return super() @@ -659,7 +656,7 @@ module ActiveRecord        end        self.where_clause = self.where_clause.or(other.where_clause) -      self.having_clause = self.having_clause.or(other.having_clause) +      self.having_clause = having_clause.or(other.having_clause)        self      end @@ -690,13 +687,6 @@ module ActiveRecord      end      def limit!(value) # :nodoc: -      if string_containing_comma?(value) -        # Remove `string_containing_comma?` when removing this deprecation -        ActiveSupport::Deprecation.warn(<<-WARNING.squish) -          Passing a string to limit in the form "1,2" is deprecated and will be -          removed in Rails 5.1. Please call `offset` explicitly instead. -        WARNING -      end        self.limit_value = value        self      end @@ -848,16 +838,12 @@ module ActiveRecord      def distinct(value = true)        spawn.distinct!(value)      end -    alias uniq distinct -    deprecate uniq: :distinct      # Like #distinct, but modifies relation in place.      def distinct!(value = true) # :nodoc:        self.distinct_value = value        self      end -    alias uniq! distinct! -    deprecate uniq!: :distinct!      # Used to extend a scope with additional methods, either through      # a module or through a block provided. @@ -958,13 +944,7 @@ module ActiveRecord          arel.where(where_clause.ast) unless where_clause.empty?          arel.having(having_clause.ast) unless having_clause.empty? -        if limit_value -          if string_containing_comma?(limit_value) -            arel.take(connection.sanitize_limit(limit_value)) -          else -            arel.take(Arel::Nodes::BindParam.new) -          end -        end +        arel.take(Arel::Nodes::BindParam.new) if limit_value          arel.skip(Arel::Nodes::BindParam.new) if offset_value          arel.group(*arel_columns(group_values.uniq.reject(&:blank?))) unless group_values.empty? @@ -1192,10 +1172,6 @@ module ActiveRecord        end        alias having_clause_factory where_clause_factory -      def string_containing_comma?(value) -        ::String === value && value.include?(",") -      end -        def default_value_for(name)          case name          when :create_with diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb index 647834b12e..427c0019c6 100644 --- a/activerecord/lib/active_record/sanitization.rb +++ b/activerecord/lib/active_record/sanitization.rb @@ -46,7 +46,7 @@ module ActiveRecord          #          #   sanitize_sql_for_assignment("name=NULL and group_id='4'")          #   # => "name=NULL and group_id='4'" -        def sanitize_sql_for_assignment(assignments, default_table_name = self.table_name) # :doc: +        def sanitize_sql_for_assignment(assignments, default_table_name = table_name) # :doc:            case assignments            when Array; sanitize_sql_array(assignments)            when Hash;  sanitize_sql_hash_for_assignment(assignments, default_table_name) diff --git a/activerecord/lib/active_record/schema.rb b/activerecord/lib/active_record/schema.rb index 7a2bc9c8af..5104427339 100644 --- a/activerecord/lib/active_record/schema.rb +++ b/activerecord/lib/active_record/schema.rb @@ -48,7 +48,7 @@ module ActiveRecord        instance_eval(&block)        if info[:version].present? -        initialize_schema_migrations_table +        ActiveRecord::SchemaMigration.create_table          connection.assume_migrated_upto_version(info[:version], migrations_paths)        end diff --git a/activerecord/lib/active_record/schema_migration.rb b/activerecord/lib/active_record/schema_migration.rb index 99b23e5593..5efbcff96a 100644 --- a/activerecord/lib/active_record/schema_migration.rb +++ b/activerecord/lib/active_record/schema_migration.rb @@ -17,7 +17,7 @@ module ActiveRecord        end        def table_exists? -        ActiveSupport::Deprecation.silence { connection.table_exists?(table_name) } +        connection.table_exists?(table_name)        end        def create_table diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb index 119372b4bf..27cdf8cb7e 100644 --- a/activerecord/lib/active_record/scoping/named.rb +++ b/activerecord/lib/active_record/scoping/named.rb @@ -171,14 +171,14 @@ module ActiveRecord            end          end -      private +        private -        def valid_scope_name?(name) # :doc: -          if respond_to?(name, true) && logger -            logger.warn "Creating scope :#{name}. " \ -                        "Overwriting existing method #{self.name}.#{name}." +          def valid_scope_name?(name) +            if respond_to?(name, true) && logger +              logger.warn "Creating scope :#{name}. " \ +                "Overwriting existing method #{self.name}.#{name}." +            end            end -        end        end      end    end diff --git a/activerecord/lib/active_record/secure_token.rb b/activerecord/lib/active_record/secure_token.rb index 7606961e2e..115799cc20 100644 --- a/activerecord/lib/active_record/secure_token.rb +++ b/activerecord/lib/active_record/secure_token.rb @@ -27,7 +27,7 @@ module ActiveRecord          # Load securerandom only when has_secure_token is used.          require "active_support/core_ext/securerandom"          define_method("regenerate_#{attribute}") { update! attribute => self.class.generate_unique_secure_token } -        before_create { self.send("#{attribute}=", self.class.generate_unique_secure_token) unless self.send("#{attribute}?") } +        before_create { send("#{attribute}=", self.class.generate_unique_secure_token) unless send("#{attribute}?") }        end        def generate_unique_secure_token diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb index d4be20d999..006afe7495 100644 --- a/activerecord/lib/active_record/store.rb +++ b/activerecord/lib/active_record/store.rb @@ -78,7 +78,7 @@ module ActiveRecord      module ClassMethods        def store(store_attribute, options = {}) -        serialize store_attribute, IndifferentCoder.new(options[:coder]) +        serialize store_attribute, IndifferentCoder.new(store_attribute, options[:coder])          store_accessor(store_attribute, options[:accessors]) if options.has_key? :accessors        end @@ -177,12 +177,12 @@ module ActiveRecord        end        class IndifferentCoder # :nodoc: -        def initialize(coder_or_class_name) +        def initialize(attr_name, coder_or_class_name)            @coder =              if coder_or_class_name.respond_to?(:load) && coder_or_class_name.respond_to?(:dump)                coder_or_class_name              else -              ActiveRecord::Coders::YAMLColumn.new(coder_or_class_name || Object) +              ActiveRecord::Coders::YAMLColumn.new(attr_name, coder_or_class_name || Object)              end          end diff --git a/activerecord/lib/active_record/table_metadata.rb b/activerecord/lib/active_record/table_metadata.rb index b618e5cfcd..71efc1829a 100644 --- a/activerecord/lib/active_record/table_metadata.rb +++ b/activerecord/lib/active_record/table_metadata.rb @@ -44,7 +44,7 @@ module ActiveRecord      end      def associated_table(table_name) -      association = klass._reflect_on_association(table_name) || klass._reflect_on_association(table_name.singularize) +      association = klass._reflect_on_association(table_name) || klass._reflect_on_association(table_name.to_s.singularize)        if !association && table_name == arel_table.name          return self diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb index c6204ac36f..82604a915f 100644 --- a/activerecord/lib/active_record/tasks/database_tasks.rb +++ b/activerecord/lib/active_record/tasks/database_tasks.rb @@ -1,5 +1,3 @@ -require "active_support/core_ext/string/filters" -  module ActiveRecord    module Tasks # :nodoc:      class DatabaseAlreadyExists < StandardError; end # :nodoc: @@ -35,6 +33,16 @@ module ActiveRecord      #      #   DatabaseTasks.create_current('production')      module DatabaseTasks +      ## +      # :singleton-method: +      # Extra flags passed to database CLI tool (mysqldump/pg_dump) when calling db:structure:dump +      mattr_accessor :structure_dump_flags, instance_accessor: false + +      ## +      # :singleton-method: +      # Extra flags passed to database CLI tool when calling db:structure:load +      mattr_accessor :structure_load_flags, instance_accessor: false +        extend self        attr_writer :current_config, :db_dir, :migrations_paths, :fixtures_path, :root, :env, :seed_loader @@ -204,13 +212,13 @@ module ActiveRecord        def structure_dump(*arguments)          configuration = arguments.first          filename = arguments.delete_at 1 -        class_for_adapter(configuration["adapter"]).new(*arguments).structure_dump(filename) +        class_for_adapter(configuration["adapter"]).new(*arguments).structure_dump(filename, structure_dump_flags)        end        def structure_load(*arguments)          configuration = arguments.first          filename = arguments.delete_at 1 -        class_for_adapter(configuration["adapter"]).new(*arguments).structure_load(filename) +        class_for_adapter(configuration["adapter"]).new(*arguments).structure_load(filename, structure_load_flags)        end        def load_schema(configuration, format = ActiveRecord::Base.schema_format, file = nil) # :nodoc: @@ -231,14 +239,6 @@ module ActiveRecord          ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Migrator.current_environment        end -      def load_schema_for(*args) -        ActiveSupport::Deprecation.warn(<<-MSG.squish) -          This method was renamed to `#load_schema` and will be removed in the future. -          Use `#load_schema` instead. -        MSG -        load_schema(*args) -      end -        def schema_file(format = ActiveRecord::Base.schema_format)          case format          when :ruby @@ -267,12 +267,22 @@ module ActiveRecord          if seed_loader            seed_loader.load_seed          else -          raise "You tried to load seed data, but no seed loader is specified. Please specify seed " + -                "loader with ActiveRecord::Tasks::DatabaseTasks.seed_loader = your_seed_loader\n" + +          raise "You tried to load seed data, but no seed loader is specified. Please specify seed " \ +                "loader with ActiveRecord::Tasks::DatabaseTasks.seed_loader = your_seed_loader\n" \                  "Seed loader should respond to load_seed method"          end        end +      # Dumps the schema cache in YAML format for the connection into the file +      # +      # ==== Examples: +      #   ActiveRecord::Tasks::DatabaseTasks.dump_schema_cache(ActiveRecord::Base.connection, "tmp/schema_dump.yaml") +      def dump_schema_cache(conn, filename) +        conn.schema_cache.clear! +        conn.data_sources.each { |table| conn.schema_cache.add(table) } +        open(filename, "wb") { |f| f.write(YAML.dump(conn.schema_cache)) } +      end +        private          def class_for_adapter(adapter) diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb index 5cdb3d53f6..920830b9cf 100644 --- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb +++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb @@ -53,21 +53,23 @@ module ActiveRecord          connection.collation        end -      def structure_dump(filename) +      def structure_dump(filename, extra_flags)          args = prepare_command_options          args.concat(["--result-file", "#{filename}"])          args.concat(["--no-data"])          args.concat(["--routines"])          args.concat(["--skip-comments"]) +        args.concat(Array(extra_flags)) if extra_flags          args.concat(["#{configuration['database']}"])          run_cmd("mysqldump", args, "dumping")        end -      def structure_load(filename) +      def structure_load(filename, extra_flags)          args = prepare_command_options          args.concat(["--execute", %{SET FOREIGN_KEY_CHECKS = 0; SOURCE #{filename}; SET FOREIGN_KEY_CHECKS = 1}])          args.concat(["--database", "#{configuration['database']}"]) +        args.concat(Array(extra_flags)) if extra_flags          run_cmd("mysql", args, "loading")        end diff --git a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb index 4e9897f7b0..5155ced0e2 100644 --- a/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb +++ b/activerecord/lib/active_record/tasks/postgresql_database_tasks.rb @@ -43,7 +43,7 @@ module ActiveRecord          create true        end -      def structure_dump(filename) +      def structure_dump(filename, extra_flags)          set_psql_env          search_path = \ @@ -57,6 +57,7 @@ module ActiveRecord            end          args = ["-s", "-x", "-O", "-f", filename] +        args.concat(Array(extra_flags)) if extra_flags          unless search_path.blank?            args += search_path.split(",").map do |part|              "--schema=#{part.strip}" @@ -67,9 +68,11 @@ module ActiveRecord          File.open(filename, "a") { |f| f << "SET search_path TO #{connection.schema_search_path};\n\n" }        end -      def structure_load(filename) +      def structure_load(filename, extra_flags)          set_psql_env -        args = [ "-v", ON_ERROR_STOP_1, "-q", "-f", filename, configuration["database"] ] +        args = ["-v", ON_ERROR_STOP_1, "-q", "-f", filename] +        args.concat(Array(extra_flags)) if extra_flags +        args << configuration["database"]          run_cmd("psql", args, "loading")        end diff --git a/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb b/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb index 31f1b7efd4..1f756c2979 100644 --- a/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb +++ b/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb @@ -21,7 +21,7 @@ module ActiveRecord          FileUtils.rm(file)        rescue Errno::ENOENT => error -        raise NoDatabaseError.new(error.message, error) +        raise NoDatabaseError.new(error.message)        end        def purge @@ -35,14 +35,16 @@ module ActiveRecord          connection.encoding        end -      def structure_dump(filename) +      def structure_dump(filename, extra_flags)          dbfile = configuration["database"] -        `sqlite3 #{dbfile} .schema > #{filename}` +        flags = extra_flags.join(" ") if extra_flags +        `sqlite3 #{flags} #{dbfile} .schema > #{filename}`        end -      def structure_load(filename) +      def structure_load(filename, extra_flags)          dbfile = configuration["database"] -        `sqlite3 #{dbfile} < "#{filename}"` +        flags = extra_flags.join(" ") if extra_flags +        `sqlite3 #{flags} #{dbfile} < "#{filename}"`        end        private diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb index 63100e38a1..09d8d1cdd4 100644 --- a/activerecord/lib/active_record/timestamp.rb +++ b/activerecord/lib/active_record/timestamp.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true  module ActiveRecord    # = Active Record \Timestamp    # @@ -51,15 +52,41 @@ module ActiveRecord        clear_timestamp_attributes      end +    class_methods do +      private +        def timestamp_attributes_for_create_in_model +          timestamp_attributes_for_create.select { |c| column_names.include?(c) } +        end + +        def timestamp_attributes_for_update_in_model +          timestamp_attributes_for_update.select { |c| column_names.include?(c) } +        end + +        def all_timestamp_attributes_in_model +          timestamp_attributes_for_create_in_model + timestamp_attributes_for_update_in_model +        end + +        def timestamp_attributes_for_create +          ["created_at", "created_on"] +        end + +        def timestamp_attributes_for_update +          ["updated_at", "updated_on"] +        end + +        def current_time_from_proper_timezone +          default_timezone == :utc ? Time.now.utc : Time.now +        end +    end +    private      def _create_record        if record_timestamps          current_time = current_time_from_proper_timezone -        all_timestamp_attributes.each do |column| -          column = column.to_s -          if has_attribute?(column) && !attribute_present?(column) +        all_timestamp_attributes_in_model.each do |column| +          if !attribute_present?(column)              write_attribute(column, current_time)            end          end @@ -73,7 +100,6 @@ module ActiveRecord          current_time = current_time_from_proper_timezone          timestamp_attributes_for_update_in_model.each do |column| -          column = column.to_s            next if will_save_change_to_attribute?(column)            write_attribute(column, current_time)          end @@ -86,30 +112,22 @@ module ActiveRecord      end      def timestamp_attributes_for_create_in_model -      timestamp_attributes_for_create.select { |c| self.class.column_names.include?(c.to_s) } +      self.class.send(:timestamp_attributes_for_create_in_model)      end      def timestamp_attributes_for_update_in_model -      timestamp_attributes_for_update.select { |c| self.class.column_names.include?(c.to_s) } +      self.class.send(:timestamp_attributes_for_update_in_model)      end      def all_timestamp_attributes_in_model -      timestamp_attributes_for_create_in_model + timestamp_attributes_for_update_in_model -    end - -    def timestamp_attributes_for_update -      [:updated_at, :updated_on] -    end - -    def timestamp_attributes_for_create -      [:created_at, :created_on] +      self.class.send(:all_timestamp_attributes_in_model)      end -    def all_timestamp_attributes -      timestamp_attributes_for_create + timestamp_attributes_for_update +    def current_time_from_proper_timezone +      self.class.send(:current_time_from_proper_timezone)      end -    def max_updated_column_timestamp(timestamp_names = timestamp_attributes_for_update) +    def max_updated_column_timestamp(timestamp_names = self.class.send(:timestamp_attributes_for_update))        timestamp_names          .map { |attr| self[attr] }          .compact @@ -117,10 +135,6 @@ module ActiveRecord          .max      end -    def current_time_from_proper_timezone -      self.class.default_timezone == :utc ? Time.now.utc : Time.now -    end -      # Clear attributes and changed_attributes      def clear_timestamp_attributes        all_timestamp_attributes_in_model.each do |attribute_name| diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index f22acd0f77..56b75540e3 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -274,16 +274,6 @@ module ActiveRecord          set_callback(:rollback_without_transaction_enrollment, :after, *args, &block)        end -      def raise_in_transactional_callbacks -        ActiveSupport::Deprecation.warn("ActiveRecord::Base.raise_in_transactional_callbacks is deprecated and will be removed without replacement.") -        true -      end - -      def raise_in_transactional_callbacks=(value) -        ActiveSupport::Deprecation.warn("ActiveRecord::Base.raise_in_transactional_callbacks= is deprecated, has no effect and will be removed without replacement.") -        value -      end -        private          def set_options_for_callbacks!(args, enforced_options = {}) diff --git a/activerecord/lib/active_record/type/serialized.rb b/activerecord/lib/active_record/type/serialized.rb index ac9134bfcb..6af05c1860 100644 --- a/activerecord/lib/active_record/type/serialized.rb +++ b/activerecord/lib/active_record/type/serialized.rb @@ -43,7 +43,7 @@ module ActiveRecord        def assert_valid_value(value)          if coder.respond_to?(:assert_valid_value) -          coder.assert_valid_value(value) +          coder.assert_valid_value(value, action: "serialize")          end        end diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index 7cc43041fe..9633f226f0 100644 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -70,11 +70,11 @@ module ActiveRecord    private -    def default_validation_context # :doc: +    def default_validation_context        new_record? ? :create : :update      end -    def raise_validation_error # :doc: +    def raise_validation_error        raise(RecordInvalid.new(self))      end diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index ae1ca9ed99..154cf5f1a4 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -53,7 +53,7 @@ module ActiveRecord          klass.unscoped.where!({ attribute => value }, options)        end -      def scope_relation(record, relation) # :doc: +      def scope_relation(record, relation)          Array(options[:scope]).each do |scope_item|            scope_value = if record.class._reflect_on_association(scope_item)              record.association(scope_item).reader @@ -66,7 +66,7 @@ module ActiveRecord          relation        end -      def map_enum_attribute(klass, attribute, value) # :doc: +      def map_enum_attribute(klass, attribute, value)          mapping = klass.defined_enums[attribute.to_s]          value = mapping[value] if value && mapping          value diff --git a/activerecord/lib/rails/generators/active_record/migration.rb b/activerecord/lib/rails/generators/active_record/migration.rb index 4263c11ffc..43075077b9 100644 --- a/activerecord/lib/rails/generators/active_record/migration.rb +++ b/activerecord/lib/rails/generators/active_record/migration.rb @@ -20,6 +20,14 @@ module ActiveRecord            key_type = options[:primary_key_type]            ", id: :#{key_type}" if key_type          end + +        def db_migrate_path +          if defined?(Rails) && Rails.application +            Rails.application.config.paths["db/migrate"].to_ary.first +          else +            "db/migrate" +          end +        end      end    end  end diff --git a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb index 1de2aff632..1f1c47499b 100644 --- a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb +++ b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb @@ -10,7 +10,7 @@ module ActiveRecord        def create_migration_file          set_local_assigns!          validate_file_name! -        migration_template @migration_template, "db/migrate/#{file_name}.rb" +        migration_template @migration_template, File.join(db_migrate_path, "#{file_name}.rb")        end        # TODO Change this to private once we've dropped Ruby 2.2 support. @@ -23,7 +23,7 @@ module ActiveRecord          # Sets the default migration template that is being used for the generation of the migration.          # Depending on command line arguments, the migration template and the table name instance          # variables are set up. -        def set_local_assigns! # :doc: +        def set_local_assigns!            @migration_template = "migration.rb"            case file_name            when /^(add|remove)_.*_(?:to|from)_(.*)/ @@ -42,13 +42,13 @@ module ActiveRecord            end          end -        def set_index_names # :doc: +        def set_index_names            attributes.each_with_index do |attr, i|              attr.index_name = [attr, attributes[i - 1]].map { |a| index_name_for(a) }            end          end -        def index_name_for(attribute) # :doc: +        def index_name_for(attribute)            if attribute.foreign_key?              attribute.name            else diff --git a/activerecord/lib/rails/generators/active_record/model/model_generator.rb b/activerecord/lib/rails/generators/active_record/model/model_generator.rb index ff76881834..5cec07d2e3 100644 --- a/activerecord/lib/rails/generators/active_record/model/model_generator.rb +++ b/activerecord/lib/rails/generators/active_record/model/model_generator.rb @@ -17,7 +17,7 @@ module ActiveRecord        def create_migration_file          return unless options[:migration] && options[:parent].nil?          attributes.each { |a| a.attr_options.delete(:index) if a.reference? && !a.has_index? } if options[:indexes] == false -        migration_template "../../migration/templates/create_table_migration.rb", "db/migrate/create_#{table_name}.rb" +        migration_template "../../migration/templates/create_table_migration.rb", File.join(db_migrate_path, "create_#{table_name}.rb")        end        def create_model_file @@ -35,29 +35,29 @@ module ActiveRecord        private -        def attributes_with_index # :doc: +        def attributes_with_index            attributes.select { |a| !a.reference? && a.has_index? }          end          # FIXME: Change this file to a symlink once RubyGems 2.5.0 is required. -        def generate_application_record # :doc: -          if self.behavior == :invoke && !application_record_exist? +        def generate_application_record +          if behavior == :invoke && !application_record_exist?              template "application_record.rb", application_record_file_name            end          end          # Used by the migration template to determine the parent name of the model -        def parent_class_name # :doc: +        def parent_class_name            options[:parent] || "ApplicationRecord"          end -        def application_record_exist? # :doc: +        def application_record_exist?            file_exist = nil            in_root { file_exist = File.exist?(application_record_file_name) }            file_exist          end -        def application_record_file_name # :doc: +        def application_record_file_name            @application_record_file_name ||= if mountable_engine?              "app/models/#{namespaced_path}/application_record.rb"            else diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index 3fce0a1df1..a2b7d53205 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -31,8 +31,7 @@ module ActiveRecord      end      def test_tables -      tables = nil -      ActiveSupport::Deprecation.silence { tables = @connection.tables } +      tables = @connection.tables        assert_includes tables, "accounts"        assert_includes tables, "authors"        assert_includes tables, "tasks" @@ -40,17 +39,11 @@ module ActiveRecord      end      def test_table_exists? -      ActiveSupport::Deprecation.silence do -        assert @connection.table_exists?("accounts") -        assert @connection.table_exists?(:accounts) -        assert_not @connection.table_exists?("nonexistingtable") -        assert_not @connection.table_exists?("'") -        assert_not @connection.table_exists?(nil) -      end -    end - -    def test_table_exists_checking_both_tables_and_views_is_deprecated -      assert_deprecated { @connection.table_exists?("accounts") } +      assert @connection.table_exists?("accounts") +      assert @connection.table_exists?(:accounts) +      assert_not @connection.table_exists?("nonexistingtable") +      assert_not @connection.table_exists?("'") +      assert_not @connection.table_exists?(nil)      end      def test_data_sources @@ -191,34 +184,6 @@ module ActiveRecord      end      unless current_adapter?(:SQLite3Adapter) -      def test_foreign_key_violations_are_translated_to_specific_exception -        error = assert_raises(ActiveRecord::InvalidForeignKey) do -          # Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method -          if @connection.prefetch_primary_key? -            id_value = @connection.next_sequence_value(@connection.default_sequence_name("fk_test_has_fk", "id")) -            @connection.execute "INSERT INTO fk_test_has_fk (id, fk_id) VALUES (#{id_value},0)" -          else -            @connection.execute "INSERT INTO fk_test_has_fk (fk_id) VALUES (0)" -          end -        end - -        assert_not_nil error.cause -      end - -      def test_foreign_key_violations_are_translated_to_specific_exception_with_validate_false -        klass_has_fk = Class.new(ActiveRecord::Base) do -          self.table_name = "fk_test_has_fk" -        end - -        error = assert_raises(ActiveRecord::InvalidForeignKey) do -          has_fk = klass_has_fk.new -          has_fk.fk_id = 1231231231 -          has_fk.save(validate: false) -        end - -        assert_not_nil error.cause -      end -        def test_value_limit_violations_are_translated_to_specific_exception          error = assert_raises(ActiveRecord::ValueTooLong) do            Event.create(title: "abcdefgh") @@ -236,23 +201,6 @@ module ActiveRecord        end      end -    def test_disable_referential_integrity -      assert_nothing_raised do -        @connection.disable_referential_integrity do -          # Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method -          if @connection.prefetch_primary_key? -            id_value = @connection.next_sequence_value(@connection.default_sequence_name("fk_test_has_fk", "id")) -            @connection.execute "INSERT INTO fk_test_has_fk (id, fk_id) VALUES (#{id_value},0)" -          else -            @connection.execute "INSERT INTO fk_test_has_fk (fk_id) VALUES (0)" -          end -          # should delete created record as otherwise disable_referential_integrity will try to enable constraints after executed block -          # and will fail (at least on Oracle) -          @connection.execute "DELETE FROM fk_test_has_fk" -        end -      end -    end -      def test_select_all_always_return_activerecord_result        result = @connection.select_all "SELECT * FROM posts"        assert result.is_a?(ActiveRecord::Result) @@ -294,16 +242,59 @@ module ActiveRecord          assert_not_nil error.message        end      end +  end -    if current_adapter?(:Mysql2Adapter, :SQLite3Adapter) -      def test_tables_returning_both_tables_and_views_is_deprecated -        assert_deprecated { @connection.tables } +  class AdapterForeignKeyTest < ActiveRecord::TestCase +    self.use_transactional_tests = false + +    def setup +      @connection = ActiveRecord::Base.connection +    end + +    def test_foreign_key_violations_are_translated_to_specific_exception_with_validate_false +      klass_has_fk = Class.new(ActiveRecord::Base) do +        self.table_name = "fk_test_has_fk"        end + +      error = assert_raises(ActiveRecord::InvalidForeignKey) do +        has_fk = klass_has_fk.new +        has_fk.fk_id = 1231231231 +        has_fk.save(validate: false) +      end + +      assert_not_nil error.cause      end -    def test_passing_arguments_to_tables_is_deprecated -      assert_deprecated { @connection.tables(:books) } +    def test_foreign_key_violations_are_translated_to_specific_exception +      error = assert_raises(ActiveRecord::InvalidForeignKey) do +        insert_into_fk_test_has_fk +      end + +      assert_not_nil error.cause      end + +    def test_disable_referential_integrity +      assert_nothing_raised do +        @connection.disable_referential_integrity do +          insert_into_fk_test_has_fk +          # should delete created record as otherwise disable_referential_integrity will try to enable constraints +          # after executed block and will fail (at least on Oracle) +          @connection.execute "DELETE FROM fk_test_has_fk" +        end +      end +    end + +    private + +      def insert_into_fk_test_has_fk +        # Oracle adapter uses prefetched primary key values from sequence and passes them to connection adapter insert method +        if @connection.prefetch_primary_key? +          id_value = @connection.next_sequence_value(@connection.default_sequence_name("fk_test_has_fk", "id")) +          @connection.execute "INSERT INTO fk_test_has_fk (id,fk_id) VALUES (#{id_value},0)" +        else +          @connection.execute "INSERT INTO fk_test_has_fk (fk_id) VALUES (0)" +        end +      end    end    class AdapterTestWithoutTransaction < ActiveRecord::TestCase diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb index 2a528b2cb1..67e1efde27 100644 --- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb @@ -92,8 +92,8 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase        assert_equal expected, actual      end -    expected = "ALTER TABLE `peaple` ADD  INDEX `index_peaple_on_last_name` USING btree (`last_name`(10)), ALGORITHM = COPY" -    actual = ActiveRecord::Base.connection.change_table(:peaple, bulk: true) do |t| +    expected = "ALTER TABLE `people` ADD  INDEX `index_people_on_last_name` USING btree (`last_name`(10)), ALGORITHM = COPY" +    actual = ActiveRecord::Base.connection.change_table(:people, bulk: true) do |t|        t.index :last_name, length: 10, using: :btree, algorithm: :copy      end      assert_equal expected, actual diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb index c1de2218e2..1f94472390 100644 --- a/activerecord/test/cases/adapters/mysql2/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb @@ -66,9 +66,10 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase    def test_execute_after_disconnect      @connection.disconnect! -    assert_raise(ActiveRecord::StatementInvalid) do +    error = assert_raise(ActiveRecord::StatementInvalid) do        @connection.execute("SELECT 1")      end +    assert_kind_of Mysql2::Error, error.cause    end    def test_quote_after_disconnect @@ -119,7 +120,7 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase      end    end -  def test_passing_arbitary_flags_to_adapter +  def test_passing_arbitrary_flags_to_adapter      run_without_connection do |orig_connection|        ActiveRecord::Base.establish_connection(orig_connection.merge(flags: Mysql2::Client::COMPRESS))        assert_equal (Mysql2::Client::COMPRESS | Mysql2::Client::FOUND_ROWS), ActiveRecord::Base.connection.raw_connection.query_options[:flags] diff --git a/activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb b/activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb index 135789a57d..c131a5169c 100644 --- a/activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb +++ b/activerecord/test/cases/adapters/mysql2/datetime_precision_quoting_test.rb @@ -6,23 +6,27 @@ class Mysql2DatetimePrecisionQuotingTest < ActiveRecord::Mysql2TestCase    end    test "microsecond precision for MySQL gte 5.6.4" do -    stub_version "5.6.4" -    assert_microsecond_precision +    stub_version "5.6.4" do +      assert_microsecond_precision +    end    end    test "no microsecond precision for MySQL lt 5.6.4" do -    stub_version "5.6.3" -    assert_no_microsecond_precision +    stub_version "5.6.3" do +      assert_no_microsecond_precision +    end    end    test "microsecond precision for MariaDB gte 5.3.0" do -    stub_version "5.5.5-10.1.8-MariaDB-log" -    assert_microsecond_precision +    stub_version "5.5.5-10.1.8-MariaDB-log" do +      assert_microsecond_precision +    end    end    test "no microsecond precision for MariaDB lt 5.3.0" do -    stub_version "5.2.9-MariaDB" -    assert_no_microsecond_precision +    stub_version "5.2.9-MariaDB" do +      assert_no_microsecond_precision +    end    end    private @@ -41,5 +45,8 @@ class Mysql2DatetimePrecisionQuotingTest < ActiveRecord::Mysql2TestCase      def stub_version(full_version_string)        @connection.stubs(:full_version).returns(full_version_string)        @connection.remove_instance_variable(:@version) if @connection.instance_variable_defined?(:@version) +      yield +    ensure +      @connection.remove_instance_variable(:@version) if @connection.instance_variable_defined?(:@version)      end  end diff --git a/activerecord/test/cases/adapters/mysql2/json_test.rb b/activerecord/test/cases/adapters/mysql2/json_test.rb index 5d4db1be91..6954006003 100644 --- a/activerecord/test/cases/adapters/mysql2/json_test.rb +++ b/activerecord/test/cases/adapters/mysql2/json_test.rb @@ -93,7 +93,7 @@ if ActiveRecord::Base.connection.supports_json?      def test_null_json        @connection.execute "insert into json_data_type (payload) VALUES(null)"        x = JsonDataType.first -      assert_equal(nil, x.payload) +      assert_nil(x.payload)      end      def test_select_array_json_value @@ -111,7 +111,7 @@ if ActiveRecord::Base.connection.supports_json?      def test_select_nil_json_after_update        json = JsonDataType.create(payload: "foo")        x = JsonDataType.where(payload: nil).first -      assert_equal(nil, x) +      assert_nil(x)        json.update_attributes payload: nil        x = JsonDataType.where(payload: nil).first diff --git a/activerecord/test/cases/adapters/mysql2/legacy_migration_test.rb b/activerecord/test/cases/adapters/mysql2/legacy_migration_test.rb deleted file mode 100644 index 5d3125c2be..0000000000 --- a/activerecord/test/cases/adapters/mysql2/legacy_migration_test.rb +++ /dev/null @@ -1,60 +0,0 @@ -require "cases/helper" - -class MysqlLegacyMigrationTest < ActiveRecord::Mysql2TestCase -  self.use_transactional_tests = false - -  class GenerateTableWithoutBigint < ActiveRecord::Migration[5.0] -    def change -      create_table :legacy_integer_pk do |table| -        table.string :foo -      end - -      create_table :override_pk, id: :bigint do |table| -        table.string :bar -      end -    end -  end - -  def setup -    super -    @connection = ActiveRecord::Base.connection - -    @migration_verbose_old = ActiveRecord::Migration.verbose -    ActiveRecord::Migration.verbose = false - -    migrations = [GenerateTableWithoutBigint.new(nil, 1)] - -    ActiveRecord::Migrator.new(:up, migrations).migrate -  end - -  def teardown -    ActiveRecord::Migration.verbose = @migration_verbose_old -    @connection.drop_table("legacy_integer_pk") -    @connection.drop_table("override_pk") -    ActiveRecord::SchemaMigration.delete_all rescue nil -    super -  end - -  def test_create_table_uses_integer_as_pkey_by_default -    col = column(:legacy_integer_pk, :id) -    assert_equal "int(11)", sql_type_for(col) -    assert col.auto_increment? -  end - -  def test_create_tables_respects_pk_column_type_override -    col = column(:override_pk, :id) -    assert_equal "bigint(20)", sql_type_for(col) -  end - -  private - -    def column(table_name, column_name) -      ActiveRecord::Base.connection -        .columns(table_name.to_s) -        .detect { |c| c.name == column_name.to_s } -    end - -    def sql_type_for(col) -      col && col.sql_type -    end -end diff --git a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb index fa54aac6b3..605baa9905 100644 --- a/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb +++ b/activerecord/test/cases/adapters/mysql2/schema_migrations_test.rb @@ -16,7 +16,7 @@ class SchemaMigrationsTest < ActiveRecord::Mysql2TestCase        table_name = ActiveRecord::SchemaMigration.table_name        connection.drop_table table_name, if_exists: true -      connection.initialize_schema_migrations_table +      ActiveRecord::SchemaMigration.create_table        assert connection.column_exists?(table_name, :version, :string, collation: "utf8_general_ci")      end @@ -27,7 +27,7 @@ class SchemaMigrationsTest < ActiveRecord::Mysql2TestCase        table_name = ActiveRecord::InternalMetadata.table_name        connection.drop_table table_name, if_exists: true -      connection.initialize_internal_metadata_table +      ActiveRecord::InternalMetadata.create_table        assert connection.column_exists?(table_name, :key, :string, collation: "utf8_general_ci")      end diff --git a/activerecord/test/cases/adapters/mysql2/virtual_column_test.rb b/activerecord/test/cases/adapters/mysql2/virtual_column_test.rb new file mode 100644 index 0000000000..442a4fb7b5 --- /dev/null +++ b/activerecord/test/cases/adapters/mysql2/virtual_column_test.rb @@ -0,0 +1,59 @@ +require "cases/helper" +require "support/schema_dumping_helper" + +if ActiveRecord::Base.connection.supports_virtual_columns? +  class Mysql2VirtualColumnTest < ActiveRecord::Mysql2TestCase +    include SchemaDumpingHelper + +    self.use_transactional_tests = false + +    class VirtualColumn < ActiveRecord::Base +    end + +    def setup +      @connection = ActiveRecord::Base.connection +      @connection.create_table :virtual_columns, force: true do |t| +        t.string  :name +        t.virtual :upper_name,  type: :string,  as: "UPPER(`name`)" +        t.virtual :name_length, type: :integer, as: "LENGTH(`name`)", stored: true +      end +      VirtualColumn.create(name: "Rails") +    end + +    def teardown +      @connection.drop_table :virtual_columns, if_exists: true +      VirtualColumn.reset_column_information +    end + +    def test_virtual_column +      column = VirtualColumn.columns_hash["upper_name"] +      assert_predicate column, :virtual? +      assert_match %r{\bVIRTUAL\b}, column.extra +      assert_equal "RAILS", VirtualColumn.take.upper_name +    end + +    def test_stored_column +      column = VirtualColumn.columns_hash["name_length"] +      assert_predicate column, :virtual? +      assert_match %r{\b(?:STORED|PERSISTENT)\b}, column.extra +      assert_equal 5, VirtualColumn.take.name_length +    end + +    def test_change_table +      @connection.change_table :virtual_columns do |t| +        t.virtual :lower_name, type: :string, as: "LOWER(name)" +      end +      VirtualColumn.reset_column_information +      column = VirtualColumn.columns_hash["lower_name"] +      assert_predicate column, :virtual? +      assert_match %r{\bVIRTUAL\b}, column.extra +      assert_equal "rails", VirtualColumn.take.lower_name +    end + +    def test_schema_dumping +      output = dump_table_schema("virtual_columns") +      assert_match(/t\.virtual\s+"upper_name",\s+type: :string,\s+as: "UPPER\(`name`\)"$/i, output) +      assert_match(/t\.virtual\s+"name_length",\s+type: :integer,\s+as: "LENGTH\(`name`\)",\s+stored: true$/i, output) +    end +  end +end diff --git a/activerecord/test/cases/adapters/postgresql/array_test.rb b/activerecord/test/cases/adapters/postgresql/array_test.rb index 680dad9706..c78c6178ff 100644 --- a/activerecord/test/cases/adapters/postgresql/array_test.rb +++ b/activerecord/test/cases/adapters/postgresql/array_test.rb @@ -16,10 +16,12 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase      @connection.transaction do        @connection.create_table("pg_arrays") do |t| -        t.string "tags", array: true +        t.string "tags", array: true, limit: 255          t.integer "ratings", array: true          t.datetime :datetimes, array: true          t.hstore :hstores, array: true +        t.decimal :decimals, array: true, default: [], precision: 10, scale: 2 +        t.timestamp :timestamps, array: true, default: [], precision: 6        end      end      PgArray.reset_column_information @@ -34,7 +36,7 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase    def test_column      assert_equal :string, @column.type -    assert_equal "character varying", @column.sql_type +    assert_equal "character varying(255)", @column.sql_type      assert @column.array?      assert_not @type.binary? @@ -110,8 +112,9 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase    def test_schema_dump_with_shorthand      output = dump_table_schema "pg_arrays" -    assert_match %r[t\.string\s+"tags",\s+array: true], output +    assert_match %r[t\.string\s+"tags",\s+limit: 255,\s+array: true], output      assert_match %r[t\.integer\s+"ratings",\s+array: true], output +    assert_match %r[t\.decimal\s+"decimals",\s+precision: 10,\s+scale: 2,\s+default: \[\],\s+array: true], output    end    def test_select_with_strings @@ -211,7 +214,7 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase      x = PgArray.create!(tags: tags)      x.reload -    assert_equal x.tags_before_type_cast, PgArray.type_for_attribute("tags").serialize(tags) +    refute x.changed?    end    def test_quoting_non_standard_delimiters @@ -219,9 +222,10 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase      oid = ActiveRecord::ConnectionAdapters::PostgreSQL::OID      comma_delim = oid::Array.new(ActiveRecord::Type::String.new, ",")      semicolon_delim = oid::Array.new(ActiveRecord::Type::String.new, ";") +    conn = PgArray.connection -    assert_equal %({"hello,",world;}), comma_delim.serialize(strings) -    assert_equal %({hello,;"world;"}), semicolon_delim.serialize(strings) +    assert_equal %({"hello,",world;}), conn.type_cast(comma_delim.serialize(strings)) +    assert_equal %({hello,;"world;"}), conn.type_cast(semicolon_delim.serialize(strings))    end    def test_mutate_array @@ -317,6 +321,15 @@ class PostgresqlArrayTest < ActiveRecord::PostgreSQLTestCase      assert_equal [arrays_of_utf8_strings], @type.deserialize(@type.serialize([arrays_of_utf8_strings]))    end +  def test_precision_is_respected_on_timestamp_columns +    time = Time.now.change(usec: 123) +    record = PgArray.create!(timestamps: [time]) + +    assert_equal 123, record.timestamps.first.usec +    record.reload +    assert_equal 123, record.timestamps.first.usec +  end +    private      def assert_cycle(field, array)        # test creation diff --git a/activerecord/test/cases/adapters/postgresql/bytea_test.rb b/activerecord/test/cases/adapters/postgresql/bytea_test.rb index dc0df8715a..5c207116c4 100644 --- a/activerecord/test/cases/adapters/postgresql/bytea_test.rb +++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb @@ -52,7 +52,7 @@ class PostgresqlByteaTest < ActiveRecord::PostgreSQLTestCase    end    def test_type_case_nil -    assert_equal(nil, @type.deserialize(nil)) +    assert_nil(@type.deserialize(nil))    end    def test_read_value @@ -66,7 +66,7 @@ class PostgresqlByteaTest < ActiveRecord::PostgreSQLTestCase    def test_read_nil_value      @connection.execute "insert into bytea_data_type (payload) VALUES (null)"      record = ByteaDataType.first -    assert_equal(nil, record.payload) +    assert_nil(record.payload)      record.delete    end @@ -106,8 +106,8 @@ class PostgresqlByteaTest < ActiveRecord::PostgreSQLTestCase    def test_write_nil      record = ByteaDataType.create(payload: nil)      assert_not record.new_record? -    assert_equal(nil, record.payload) -    assert_equal(nil, ByteaDataType.where(id: record.id).first.payload) +    assert_nil(record.payload) +    assert_nil(ByteaDataType.where(id: record.id).first.payload)    end    class Serializer diff --git a/activerecord/test/cases/adapters/postgresql/connection_test.rb b/activerecord/test/cases/adapters/postgresql/connection_test.rb index 45aa748ffc..3cbd4ca212 100644 --- a/activerecord/test/cases/adapters/postgresql/connection_test.rb +++ b/activerecord/test/cases/adapters/postgresql/connection_test.rb @@ -90,17 +90,17 @@ module ActiveRecord      end      def test_tables_logs_name -      ActiveSupport::Deprecation.silence { @connection.tables("hello") } +      @connection.tables        assert_equal "SCHEMA", @subscriber.logged[0][1]      end      def test_indexes_logs_name -      @connection.indexes("items", "hello") +      assert_deprecated { @connection.indexes("items", "hello") }        assert_equal "SCHEMA", @subscriber.logged[0][1]      end      def test_table_exists_logs_name -      ActiveSupport::Deprecation.silence { @connection.table_exists?("items") } +      @connection.table_exists?("items")        assert_equal "SCHEMA", @subscriber.logged[0][1]      end @@ -156,7 +156,7 @@ module ActiveRecord          secondary_connection.query("select pg_terminate_backend(#{original_connection_pid.first.first})")          ActiveRecord::Base.connection_pool.checkin(secondary_connection)        elsif ARTest.config["with_manual_interventions"] -        puts "Kill the connection now (e.g. by restarting the PostgreSQL " + +        puts "Kill the connection now (e.g. by restarting the PostgreSQL " \            'server with the "-m fast" option) and then press enter.'          $stdin.gets        else @@ -175,7 +175,7 @@ module ActiveRecord        new_connection_pid = @connection.query("select pg_backend_pid()")        assert_not_equal original_connection_pid, new_connection_pid, -        "umm -- looks like you didn't break the connection, because we're still " + +        "umm -- looks like you didn't break the connection, because we're still " \          "successfully querying with the same connection pid."        # Repair all fixture connections so other tests won't break. diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb index 1f35300739..f9cce10fb8 100644 --- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb +++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb @@ -171,6 +171,25 @@ if ActiveRecord::Base.connection.supports_extensions?        assert_not hstore.changed?      end +    def test_dirty_from_user_equal +      settings = { "alongkey" => "anything", "key" => "value" } +      hstore = Hstore.create!(settings: settings) + +      hstore.settings = { "key" => "value", "alongkey" => "anything" } +      assert_equal settings, hstore.settings +      refute hstore.changed? +    end + +    def test_hstore_dirty_from_database_equal +      settings = { "alongkey" => "anything", "key" => "value" } +      hstore = Hstore.create!(settings: settings) +      hstore.reload + +      assert_equal settings, hstore.settings +      hstore.settings = settings +      refute hstore.changed? +    end +      def test_gen1        assert_equal('" "=>""', @type.serialize(" " => ""))      end diff --git a/activerecord/test/cases/adapters/postgresql/infinity_test.rb b/activerecord/test/cases/adapters/postgresql/infinity_test.rb index 19b00258b6..b9e177e6ec 100644 --- a/activerecord/test/cases/adapters/postgresql/infinity_test.rb +++ b/activerecord/test/cases/adapters/postgresql/infinity_test.rb @@ -30,7 +30,7 @@ class PostgresqlInfinityTest < ActiveRecord::PostgreSQLTestCase      record = PostgresqlInfinity.new(float: "-Infinity")      assert_equal(-Float::INFINITY, record.float)      record = PostgresqlInfinity.new(float: "NaN") -    assert_send [record.float, :nan?] +    assert record.float.nan?, "Expected #{record.float} to be NaN"    end    test "update_all with infinity on a float column" do diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb index 991dedfdf1..93558ac4d2 100644 --- a/activerecord/test/cases/adapters/postgresql/json_test.rb +++ b/activerecord/test/cases/adapters/postgresql/json_test.rb @@ -110,7 +110,7 @@ module PostgresqlJSONSharedTestCases    def test_null_json      @connection.execute "insert into json_data_type (payload) VALUES(null)"      x = JsonDataType.first -    assert_equal(nil, x.payload) +    assert_nil(x.payload)    end    def test_select_nil_json_after_create @@ -122,7 +122,7 @@ module PostgresqlJSONSharedTestCases    def test_select_nil_json_after_update      json = JsonDataType.create(payload: "foo")      x = JsonDataType.where(payload: nil).first -    assert_equal(nil, x) +    assert_nil(x)      json.update_attributes payload: nil      x = JsonDataType.where(payload: nil).first diff --git a/activerecord/test/cases/adapters/postgresql/legacy_migration_test.rb b/activerecord/test/cases/adapters/postgresql/legacy_migration_test.rb deleted file mode 100644 index 082fe95053..0000000000 --- a/activerecord/test/cases/adapters/postgresql/legacy_migration_test.rb +++ /dev/null @@ -1,54 +0,0 @@ -require "cases/helper" - -class PostgresqlLegacyMigrationTest < ActiveRecord::PostgreSQLTestCase -  class GenerateTableWithoutBigserial < ActiveRecord::Migration[5.0] -    def change -      create_table :legacy_integer_pk do |table| -        table.string :foo -      end - -      create_table :override_pk, id: :bigint do |table| -        table.string :bar -      end -    end -  end - -  def setup -    super - -    @migration_verbose_old = ActiveRecord::Migration.verbose -    ActiveRecord::Migration.verbose = false - -    migrations = [GenerateTableWithoutBigserial.new(nil, 1)] -    ActiveRecord::Migrator.new(:up, migrations).migrate -  end - -  def teardown -    ActiveRecord::Migration.verbose = @migration_verbose_old - -    super -  end - -  def test_create_table_uses_serial_as_pkey_by_default -    col = column(:legacy_integer_pk, :id) -    assert_equal "integer", sql_type_for(col) -    assert col.serial? -  end - -  def test_create_tables_respects_pk_column_type_override -    col = column(:override_pk, :id) -    assert_equal "bigint", sql_type_for(col) -  end - -  private - -    def column(table_name, column_name) -      ActiveRecord::Base.connection. -        columns(table_name.to_s). -        detect { |c| c.name == column_name.to_s } -    end - -    def sql_type_for(col) -      col && col.sql_type -    end -end diff --git a/activerecord/test/cases/adapters/postgresql/numbers_test.rb b/activerecord/test/cases/adapters/postgresql/numbers_test.rb index 834354dcc9..bfb2b7c27a 100644 --- a/activerecord/test/cases/adapters/postgresql/numbers_test.rb +++ b/activerecord/test/cases/adapters/postgresql/numbers_test.rb @@ -31,7 +31,7 @@ class PostgresqlNumberTest < ActiveRecord::PostgreSQLTestCase      assert_equal 123456.789, first.double      assert_equal(-::Float::INFINITY, second.single)      assert_equal ::Float::INFINITY, second.double -    assert_send [third.double, :nan?] +    assert third.double.nan?, "Expected #{third.double} to be NaN"    end    def test_update diff --git a/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb b/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb index bd45a9daa0..784d77a8d1 100644 --- a/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb +++ b/activerecord/test/cases/adapters/postgresql/type_lookup_test.rb @@ -19,7 +19,7 @@ class PostgresqlTypeLookupTest < ActiveRecord::PostgreSQLTestCase      big_array = [123456789123456789]      assert_raises(ActiveModel::RangeError) { int_array.serialize(big_array) } -    assert_equal "{123456789123456789}", bigint_array.serialize(big_array) +    assert_equal "{123456789123456789}", @connection.type_cast(bigint_array.serialize(big_array))    end    test "range types correctly respect registration of subtypes" do diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb index c6b84482d6..4655cd1d20 100644 --- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb +++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb @@ -71,12 +71,12 @@ class PostgresqlUUIDTest < ActiveRecord::PostgreSQLTestCase    def test_treat_blank_uuid_as_nil      UUIDType.create! guid: "" -    assert_equal(nil, UUIDType.last.guid) +    assert_nil(UUIDType.last.guid)    end    def test_treat_invalid_uuid_as_nil      uuid = UUIDType.create! guid: "foobar" -    assert_equal(nil, uuid.guid) +    assert_nil(uuid.guid)    end    def test_invalid_uuid_dont_modify_before_type_cast @@ -234,25 +234,23 @@ class PostgresqlUUIDGenerationTest < ActiveRecord::PostgreSQLTestCase        end      end -    if ActiveRecord::Base.connection.supports_pgcrypto_uuid? -      def test_schema_dumper_for_uuid_primary_key_default_in_legacy_migration -        @verbose_was = ActiveRecord::Migration.verbose -        ActiveRecord::Migration.verbose = false - -        migration = Class.new(ActiveRecord::Migration[4.2]) do -          def version; 101 end -          def migrate(x) -            create_table("pg_uuids_4", id: :uuid) -          end -        end.new -        ActiveRecord::Migrator.new(:up, [migration]).migrate - -        schema = dump_table_schema "pg_uuids_4" -        assert_match(/\bcreate_table "pg_uuids_4", id: :uuid, default: -> { "uuid_generate_v4\(\)" }/, schema) -      ensure -        drop_table "pg_uuids_4" -        ActiveRecord::Migration.verbose = @verbose_was -      end +    def test_schema_dumper_for_uuid_primary_key_default_in_legacy_migration +      @verbose_was = ActiveRecord::Migration.verbose +      ActiveRecord::Migration.verbose = false + +      migration = Class.new(ActiveRecord::Migration[5.0]) do +        def version; 101 end +        def migrate(x) +          create_table("pg_uuids_4", id: :uuid) +        end +      end.new +      ActiveRecord::Migrator.new(:up, [migration]).migrate + +      schema = dump_table_schema "pg_uuids_4" +      assert_match(/\bcreate_table "pg_uuids_4", id: :uuid, default: -> { "uuid_generate_v4\(\)" }/, schema) +    ensure +      drop_table "pg_uuids_4" +      ActiveRecord::Migration.verbose = @verbose_was      end    end  end @@ -285,6 +283,25 @@ class PostgresqlUUIDTestNilDefault < ActiveRecord::PostgreSQLTestCase        schema = dump_table_schema "pg_uuids"        assert_match(/\bcreate_table "pg_uuids", id: :uuid, default: nil/, schema)      end + +    def test_schema_dumper_for_uuid_primary_key_with_default_nil_in_legacy_migration +      @verbose_was = ActiveRecord::Migration.verbose +      ActiveRecord::Migration.verbose = false + +      migration = Class.new(ActiveRecord::Migration[5.0]) do +        def version; 101 end +        def migrate(x) +          create_table("pg_uuids_4", id: :uuid, default: nil) +        end +      end.new +      ActiveRecord::Migrator.new(:up, [migration]).migrate + +      schema = dump_table_schema "pg_uuids_4" +      assert_match(/\bcreate_table "pg_uuids_4", id: :uuid, default: nil/, schema) +    ensure +      drop_table "pg_uuids_4" +      ActiveRecord::Migration.verbose = @verbose_was +    end    end  end diff --git a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb index 91967c1e33..e1cfd703e8 100644 --- a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb @@ -41,8 +41,8 @@ class CopyTableTest < ActiveRecord::SQLite3TestCase      test_copy_table("comments", "comments_with_index") do        @connection.add_index("comments_with_index", ["post_id", "type"])        test_copy_table("comments_with_index", "comments_with_index2") do -        assert_equal table_indexes_without_name("comments_with_index"), -                     table_indexes_without_name("comments_with_index2") +        assert_nil table_indexes_without_name("comments_with_index") +        assert_nil table_indexes_without_name("comments_with_index2")        end      end    end @@ -59,7 +59,8 @@ class CopyTableTest < ActiveRecord::SQLite3TestCase        copied_id = @connection.columns("goofy_string_id2").detect { |col| col.name == "id" }        assert_equal original_id.type, copied_id.type        assert_equal original_id.sql_type, copied_id.sql_type -      assert_equal original_id.limit, copied_id.limit +      assert_nil original_id.limit +      assert_nil copied_id.limit      end    end diff --git a/activerecord/test/cases/adapters/sqlite3/legacy_migration_test.rb b/activerecord/test/cases/adapters/sqlite3/legacy_migration_test.rb deleted file mode 100644 index fcca8d66b5..0000000000 --- a/activerecord/test/cases/adapters/sqlite3/legacy_migration_test.rb +++ /dev/null @@ -1,59 +0,0 @@ -require "cases/helper" - -class SqliteLegacyMigrationTest < ActiveRecord::SQLite3TestCase -  self.use_transactional_tests = false - -  class GenerateTableWithoutBigint < ActiveRecord::Migration[5.0] -    def change -      create_table :legacy_integer_pk do |table| -        table.string :foo -      end - -      create_table :override_pk, id: :bigint do |table| -        table.string :bar -      end -    end -  end - -  def setup -    super -    @connection = ActiveRecord::Base.connection - -    @migration_verbose_old = ActiveRecord::Migration.verbose -    ActiveRecord::Migration.verbose = false - -    migrations = [GenerateTableWithoutBigint.new(nil, 1)] - -    ActiveRecord::Migrator.new(:up, migrations).migrate -  end - -  def teardown -    ActiveRecord::Migration.verbose = @migration_verbose_old -    @connection.drop_table("legacy_integer_pk") -    @connection.drop_table("override_pk") -    ActiveRecord::SchemaMigration.delete_all rescue nil -    super -  end - -  def test_create_table_uses_integer_as_pkey_by_default -    col = column(:legacy_integer_pk, :id) -    assert_equal "INTEGER", sql_type_for(col) -    assert primary_key?(:legacy_integer_pk, "id"), "id is not primary key" -  end - -  private - -    def column(table_name, column_name) -      ActiveRecord::Base.connection -        .columns(table_name.to_s) -        .detect { |c| c.name == column_name.to_s } -    end - -    def sql_type_for(col) -      col && col.sql_type -    end - -    def primary_key?(table_name, column) -      ActiveRecord::Base.connection.execute("PRAGMA table_info(#{table_name})").find { |col| col["name"] == column }["pk"] == 1 -    end -end diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb index 0526191952..a6afb7816b 100644 --- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb @@ -267,9 +267,9 @@ module ActiveRecord        def test_tables          with_example_table do -          ActiveSupport::Deprecation.silence { assert_equal %w{ ex }, @conn.tables } +          assert_equal %w{ ex }, @conn.tables            with_example_table "id integer PRIMARY KEY AUTOINCREMENT, number integer", "people" do -            ActiveSupport::Deprecation.silence { assert_equal %w{ ex people }.sort, @conn.tables.sort } +            assert_equal %w{ ex people }.sort, @conn.tables.sort            end          end        end @@ -277,19 +277,17 @@ module ActiveRecord        def test_tables_logs_name          sql = <<-SQL            SELECT name FROM sqlite_master -          WHERE type IN ('table','view') AND name <> 'sqlite_sequence' +          WHERE type = 'table' AND name <> 'sqlite_sequence'          SQL          assert_logged [[sql.squish, "SCHEMA", []]] do -          ActiveSupport::Deprecation.silence do -            @conn.tables("hello") -          end +          @conn.tables          end        end        def test_indexes_logs_name          with_example_table do            assert_logged [["PRAGMA index_list(\"ex\")", "SCHEMA", []]] do -            @conn.indexes("ex", "hello") +            assert_deprecated { @conn.indexes("ex", "hello") }            end          end        end @@ -298,12 +296,10 @@ module ActiveRecord          with_example_table do            sql = <<-SQL              SELECT name FROM sqlite_master -            WHERE type IN ('table','view') AND name <> 'sqlite_sequence' AND name = 'ex' +            WHERE type = 'table' AND name <> 'sqlite_sequence' AND name = 'ex'            SQL            assert_logged [[sql.squish, "SCHEMA", []]] do -            ActiveSupport::Deprecation.silence do -              assert @conn.table_exists?("ex") -            end +            assert @conn.table_exists?("ex")            end          end        end diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index b75e2a47a6..5875a1871f 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -1131,12 +1131,6 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase      Column.create! record: record      assert_equal 1, Column.count    end - -  def test_association_force_reload_with_only_true_is_deprecated -    client = Client.find(3) - -    assert_deprecated { client.firm(true) } -  end  end  class BelongsToWithForeignKeyTest < ActiveRecord::TestCase diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index ff1bf8acd4..11f4aae5b3 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -739,18 +739,25 @@ class EagerAssociationTest < ActiveRecord::TestCase    end    def test_eager_with_invalid_association_reference -    assert_raise(ActiveRecord::AssociationNotFoundError, "Association was not found; perhaps you misspelled it?  You specified :include => :monkeys") { +    e = assert_raise(ActiveRecord::AssociationNotFoundError) {        Post.all.merge!(includes: :monkeys).find(6)      } -    assert_raise(ActiveRecord::AssociationNotFoundError, "Association was not found; perhaps you misspelled it?  You specified :include => :monkeys") { +    assert_equal("Association named 'monkeys' was not found on Post; perhaps you misspelled it?", e.message) + +    e = assert_raise(ActiveRecord::AssociationNotFoundError) {        Post.all.merge!(includes: [ :monkeys ]).find(6)      } -    assert_raise(ActiveRecord::AssociationNotFoundError, "Association was not found; perhaps you misspelled it?  You specified :include => :monkeys") { +    assert_equal("Association named 'monkeys' was not found on Post; perhaps you misspelled it?", e.message) + +    e = assert_raise(ActiveRecord::AssociationNotFoundError) {        Post.all.merge!(includes: [ "monkeys" ]).find(6)      } -    assert_raise(ActiveRecord::AssociationNotFoundError, "Association was not found; perhaps you misspelled it?  You specified :include => :monkeys, :elephants") { +    assert_equal("Association named 'monkeys' was not found on Post; perhaps you misspelled it?", e.message) + +    e = assert_raise(ActiveRecord::AssociationNotFoundError) {        Post.all.merge!(includes: [ :monkeys, :elephants ]).find(6)      } +    assert_equal("Association named 'monkeys' was not found on Post; perhaps you misspelled it?", e.message)    end    def test_eager_has_many_through_with_order @@ -933,7 +940,11 @@ class EagerAssociationTest < ActiveRecord::TestCase      d2 = find_all_ordered(Firm, :account)      d1.each_index do |i|        assert_equal(d1[i], d2[i]) -      assert_equal(d1[i].account, d2[i].account) +      if d1[i].account.nil? +        assert_nil(d2[i].account) +      else +        assert_equal(d1[i].account, d2[i].account) +      end      end    end @@ -943,7 +954,13 @@ class EagerAssociationTest < ActiveRecord::TestCase      d2 = find_all_ordered(Client, firm_types)      d1.each_index do |i|        assert_equal(d1[i], d2[i]) -      firm_types.each { |type| assert_equal(d1[i].send(type), d2[i].send(type)) } +      firm_types.each do |type| +        if (expected = d1[i].send(type)).nil? +          assert_nil(d2[i].send(type)) +        else +          assert_equal(expected, d2[i].send(type)) +        end +      end      end    end    def test_eager_with_valid_association_as_string_not_symbol diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb index 4b7ac594cf..d6b595d7e7 100644 --- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb @@ -86,8 +86,10 @@ class DeveloperWithSymbolClassName < Developer    has_and_belongs_to_many :projects, class_name: :ProjectWithSymbolsForKeys  end -class DeveloperWithConstantClassName < Developer -  has_and_belongs_to_many :projects, class_name: ProjectWithSymbolsForKeys +ActiveSupport::Deprecation.silence do +  class DeveloperWithConstantClassName < Developer +    has_and_belongs_to_many :projects, class_name: ProjectWithSymbolsForKeys +  end  end  class DeveloperWithExtendOption < Developer @@ -743,8 +745,8 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase    end    def test_find_scoped_grouped_having -    assert_equal 2, projects(:active_record).well_payed_salary_groups.to_a.size -    assert projects(:active_record).well_payed_salary_groups.all? { |g| g.salary > 10000 } +    assert_equal 2, projects(:active_record).well_paid_salary_groups.to_a.size +    assert projects(:active_record).well_paid_salary_groups.all? { |g| g.salary > 10000 }    end    def test_get_ids @@ -955,12 +957,6 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase      end    end -  def test_association_force_reload_with_only_true_is_deprecated -    developer = Developer.find(1) - -    assert_deprecated { developer.projects(true) } -  end -    def test_alternate_database      professor = Professor.create(name: "Plum")      course = Course.create(name: "Forensics") diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 558c4b3ed1..cbecfa84ff 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -788,13 +788,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase      assert_equal [1], posts(:welcome).comments.select { |c| c.id == 1 }.map(&:id)    end -  def test_select_with_block_and_specific_attributes -    assert_deprecated do -      comments = posts(:welcome).comments.select(:id, :body) { |c| c.id == 1 } -      assert_equal [1], comments.map(&:id) -    end -  end -    def test_select_without_foreign_key      assert_equal companies(:first_firm).accounts.first.credit_limit, companies(:first_firm).accounts.select(:credit_limit).first.credit_limit    end @@ -1582,26 +1575,6 @@ class HasManyAssociationsTest < ActiveRecord::TestCase      assert firm.companies.exists?(name: "child")    end -  def test_restrict_with_error_is_deprecated_using_key_many -    I18n.backend = I18n::Backend::Simple.new -    I18n.backend.store_translations :en, activerecord: { errors: { messages: { restrict_dependent_destroy: { many: "message for deprecated key" } } } } - -    firm = RestrictedWithErrorFirm.create!(name: "restrict") -    firm.companies.create(name: "child") - -    assert !firm.companies.empty? - -    assert_deprecated { firm.destroy } - -    assert !firm.errors.empty? - -    assert_equal "message for deprecated key", firm.errors[:base].first -    assert RestrictedWithErrorFirm.exists?(name: "restrict") -    assert firm.companies.exists?(name: "child") -  ensure -    I18n.backend.reload! -  end -    def test_restrict_with_error      firm = RestrictedWithErrorFirm.create!(name: "restrict")      firm.companies.create(name: "child") @@ -2475,19 +2448,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase    test "double insertion of new object to association when same association used in the after create callback of a new object" do      reset_callbacks(:save, Bulb) do -      Bulb.after_save { |record| record.car.bulbs.to_a } +      Bulb.after_save { |record| record.car.bulbs.load }        car = Car.create!        car.bulbs << Bulb.new        assert_equal 1, car.bulbs.size      end    end -  def test_association_force_reload_with_only_true_is_deprecated -    company = Company.find(1) - -    assert_deprecated { company.clients_of_firm(true) } -  end -    class AuthorWithErrorDestroyingAssociation < ActiveRecord::Base      self.table_name = "authors"      has_many :posts_with_error_destroying, diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb index fd79d4a5f9..25feae910b 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -28,6 +28,9 @@ require "models/member"  require "models/membership"  require "models/club"  require "models/organization" +require "models/user" +require "models/family" +require "models/family_tree"  class HasManyThroughAssociationsTest < ActiveRecord::TestCase    fixtures :posts, :readers, :people, :comments, :authors, :categories, :taggings, :tags, @@ -1204,12 +1207,6 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase      assert_nil Club.new.special_favourites.distinct_value    end -  def test_association_force_reload_with_only_true_is_deprecated -    post = Post.find(1) - -    assert_deprecated { post.people(true) } -  end -    def test_has_many_through_do_not_cache_association_reader_if_the_though_method_has_default_scopes      member = Member.create!      club = Club.create! @@ -1237,4 +1234,23 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase    ensure      TenantMembership.current_member = nil    end + +  def test_has_many_through_with_scope_should_respect_table_alias +    family = Family.create! +    users = 3.times.map { User.create! } +    FamilyTree.create!(member: users[0], family: family) +    FamilyTree.create!(member: users[1], family: family) +    FamilyTree.create!(member: users[2], family: family, token: "wat") + +    assert_equal 2, users[0].family_members.to_a.size +    assert_equal 0, users[2].family_members.to_a.size +  end + +  def test_incorrectly_ordered_through_associations +    assert_raises(ActiveRecord::HasManyThroughOrderError) do +      DeveloperWithIncorrectlyOrderedHasManyThrough.create( +        companies: [Company.create] +      ) +    end +  end  end diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index 48fbc5990c..7c11d2e7fc 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -186,25 +186,6 @@ class HasOneAssociationsTest < ActiveRecord::TestCase      assert firm.account.present?    end -  def test_restrict_with_error_is_deprecated_using_key_one -    I18n.backend = I18n::Backend::Simple.new -    I18n.backend.store_translations :en, activerecord: { errors: { messages: { restrict_dependent_destroy: { one: "message for deprecated key" } } } } - -    firm = RestrictedWithErrorFirm.create!(name: "restrict") -    firm.create_account(credit_limit: 10) - -    assert_not_nil firm.account - -    assert_deprecated { firm.destroy } - -    assert !firm.errors.empty? -    assert_equal "message for deprecated key", firm.errors[:base].first -    assert RestrictedWithErrorFirm.exists?(name: "restrict") -    assert firm.account.present? -  ensure -    I18n.backend.reload! -  end -    def test_restrict_with_error      firm = RestrictedWithErrorFirm.create!(name: "restrict")      firm.create_account(credit_limit: 10) @@ -495,7 +476,7 @@ class HasOneAssociationsTest < ActiveRecord::TestCase      assert_equal ships(:black_pearl), pirate.ship      assert_equal pirate.id, pirate.ship.pirate_id -    assert_equal "Failed to remove the existing associated ship. " + +    assert_equal "Failed to remove the existing associated ship. " \                   "The record failed to save after its foreign key was set to nil.", error.message    end @@ -664,15 +645,10 @@ class HasOneAssociationsTest < ActiveRecord::TestCase      end    end -  def test_association_force_reload_with_only_true_is_deprecated -    firm = Firm.find(1) - -    assert_deprecated { firm.account(true) } -  end -    class SpecialBook < ActiveRecord::Base      self.table_name = "books"      belongs_to :author, class_name: "SpecialAuthor" +    has_one :subscription, class_name: "SpecialSupscription", foreign_key: "subscriber_id"    end    class SpecialAuthor < ActiveRecord::Base @@ -680,11 +656,27 @@ class HasOneAssociationsTest < ActiveRecord::TestCase      has_one :book, class_name: "SpecialBook", foreign_key: "author_id"    end -  def test_assocation_enum_works_properly +  class SpecialSupscription < ActiveRecord::Base +    self.table_name = "subscriptions" +    belongs_to :book, class_name: "SpecialBook" +  end + +  def test_association_enum_works_properly      author = SpecialAuthor.create!(name: "Test")      book = SpecialBook.create!(status: "published")      author.book = book      refute_equal 0, SpecialAuthor.joins(:book).where(books: { status: "published" }).count    end + +  def test_association_enum_works_properly_with_nested_join +    author = SpecialAuthor.create!(name: "Test") +    book = SpecialBook.create!(status: "published") +    author.book = book + +    where_clause = { books: { subscriptions: { subscriber_id: nil } } } +    assert_nothing_raised do +      SpecialAuthor.joins(book: :subscription).where.not(where_clause) +    end +  end  end diff --git a/activerecord/test/cases/associations/has_one_through_associations_test.rb b/activerecord/test/cases/associations/has_one_through_associations_test.rb index 432c3526a5..38a729d2d4 100644 --- a/activerecord/test/cases/associations/has_one_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb @@ -86,6 +86,13 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase      assert_nil @member.club    end +  def test_set_record_after_delete_association +    @member.club = nil +    @member.club = clubs(:moustache_club) +    @member.reload +    assert_equal clubs(:moustache_club), @member.club +  end +    def test_has_one_through_polymorphic      assert_equal clubs(:moustache_club), @member.sponsor_club    end diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb index 6fe6ee6783..287b3e9ebc 100644 --- a/activerecord/test/cases/associations/inverse_associations_test.rb +++ b/activerecord/test/cases/associations/inverse_associations_test.rb @@ -443,7 +443,7 @@ class InverseHasManyTests < ActiveRecord::TestCase      assert man.equal?(man.interests.first.man), "Two inverses should lead back to the same object that was originally held"      assert man.equal?(man.interests.find(interest.id).man), "Two inversions should lead back to the same object that was originally held" -    assert_equal man.name, man.interests.find(interest.id).man.name, "The name of the man should match before the name is changed" +    assert_nil man.interests.find(interest.id).man.name, "The name of the man should match before the name is changed"      man.name = "Ben Bitdiddle"      assert_equal man.name, man.interests.find(interest.id).man.name, "The name of the man should match after the parent name is changed"      man.interests.find(interest.id).man.name = "Alyssa P. Hacker" diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb index c095b3a91c..a223b4338f 100644 --- a/activerecord/test/cases/associations_test.rb +++ b/activerecord/test/cases/associations_test.rb @@ -88,10 +88,10 @@ class AssociationsTest < ActiveRecord::TestCase      assert firm.clients.empty?, "New firm should have cached no client objects"      assert_equal 0, firm.clients.size, "New firm should have cached 0 clients count" -    ActiveSupport::Deprecation.silence do -      assert !firm.clients(true).empty?, "New firm should have reloaded client objects" -      assert_equal 1, firm.clients(true).size, "New firm should have reloaded clients count" -    end +    firm.clients.reload + +    assert !firm.clients.empty?, "New firm should have reloaded client objects" +    assert_equal 1, firm.clients.size, "New firm should have reloaded clients count"    end    def test_using_limitable_reflections_helper @@ -104,19 +104,6 @@ class AssociationsTest < ActiveRecord::TestCase      assert !using_limitable_reflections.call(mixed_reflections), "No collection associations (has many style) should pass"    end -  def test_force_reload_is_uncached -    firm = Firm.create!("name" => "A New Firm, Inc") -    Client.create!("name" => "TheClient.com", :firm => firm) - -    ActiveSupport::Deprecation.silence do -      ActiveRecord::Base.cache do -        firm.clients.each {} -        assert_queries(0) { assert_not_nil firm.clients.each {} } -        assert_queries(1) { assert_not_nil firm.clients(true).each {} } -      end -    end -  end -    def test_association_with_references      firm = companies(:first_firm)      assert_includes firm.association_with_references.references_values, "foo" diff --git a/activerecord/test/cases/attribute_methods/read_test.rb b/activerecord/test/cases/attribute_methods/read_test.rb index 978dd93fa4..1fc63a49d4 100644 --- a/activerecord/test/cases/attribute_methods/read_test.rb +++ b/activerecord/test/cases/attribute_methods/read_test.rb @@ -3,7 +3,7 @@ require "cases/helper"  module ActiveRecord    module AttributeMethods      class ReadTest < ActiveRecord::TestCase -      class FakeColumn < Struct.new(:name) +      FakeColumn = Struct.new(:name) do          def type; :integer; end        end diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index 77ee3ca2d7..2203aa1788 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -36,11 +36,11 @@ class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase        private -      def should_be_cool -        unless self.first_name == "cool" -          errors.add :first_name, "not cool" +        def should_be_cool +          unless first_name == "cool" +            errors.add :first_name, "not cool" +          end          end -      end      }      reference = Class.new(ActiveRecord::Base) {        self.table_name = "references" @@ -1391,6 +1391,14 @@ module AutosaveAssociationOnACollectionAssociationTests      assert_equal "Squawky", parrot.reload.name    end +  def test_should_not_update_children_when_parent_creation_with_no_reason +    parrot = Parrot.create!(name: "Polly") +    assert_equal 0, parrot.updated_count + +    Pirate.create!(parrot_ids: [parrot.id], catchphrase: "Arrrr") +    assert_equal 0, parrot.reload.updated_count +  end +    def test_should_automatically_validate_the_associated_models      @pirate.send(@association_name).each { |child| child.name = "" } diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index ad9bc60944..a611cc208c 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -4,6 +4,7 @@ require "models/author"  require "models/topic"  require "models/reply"  require "models/category" +require "models/categorization"  require "models/company"  require "models/customer"  require "models/developer" @@ -33,8 +34,6 @@ class SecondAbstractClass < FirstAbstractClass    self.abstract_class = true  end  class Photo < SecondAbstractClass; end -class Category < ActiveRecord::Base; end -class Categorization < ActiveRecord::Base; end  class Smarts < ActiveRecord::Base; end  class CreditCard < ActiveRecord::Base    class PinNumber < ActiveRecord::Base @@ -45,8 +44,6 @@ class CreditCard < ActiveRecord::Base    class Brand < Category; end  end  class MasterCreditCard < ActiveRecord::Base; end -class Post < ActiveRecord::Base; end -class Computer < ActiveRecord::Base; end  class NonExistentTable < ActiveRecord::Base; end  class TestOracleDefault < ActiveRecord::Base; end @@ -56,12 +53,6 @@ end  class Weird < ActiveRecord::Base; end -class Boolean < ActiveRecord::Base -  def has_fun -    super -  end -end -  class LintTest < ActiveRecord::TestCase    include ActiveModel::Lint::Tests @@ -107,14 +98,6 @@ class BasicsTest < ActiveRecord::TestCase      assert_nil Edge.primary_key    end -  unless current_adapter?(:PostgreSQLAdapter, :OracleAdapter, :SQLServerAdapter, :FbAdapter) -    def test_limit_with_comma -      assert_deprecated do -        assert Topic.limit("1,2").to_a -      end -    end -  end -    def test_many_mutations      car = Car.new name: "<3<3<3"      car.engines_count = 0 @@ -144,10 +127,8 @@ class BasicsTest < ActiveRecord::TestCase    end    def test_limit_should_sanitize_sql_injection_for_limit_with_commas -    assert_deprecated do -      assert_raises(ArgumentError) do -        Topic.limit("1, 7 procedure help()").to_a -      end +    assert_raises(ArgumentError) do +      Topic.limit("1, 7 procedure help()").to_a      end    end @@ -1379,12 +1360,6 @@ class BasicsTest < ActiveRecord::TestCase      end    end -  def test_uniq_delegates_to_scoped -    assert_deprecated do -      assert_equal Bird.all.distinct, Bird.uniq -    end -  end -    def test_distinct_delegates_to_scoped      assert_equal Bird.all.distinct, Bird.distinct    end diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index 87e99fb25c..0de2fea4c2 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -13,9 +13,9 @@ require "models/speedometer"  require "models/ship_part"  require "models/treasure"  require "models/developer" +require "models/post"  require "models/comment"  require "models/rating" -require "models/post"  class NumericData < ActiveRecord::Base    self.table_name = "numeric_data" @@ -166,14 +166,15 @@ class CalculationsTest < ActiveRecord::TestCase    end    def test_limit_should_apply_before_count -    accounts = Account.limit(3).where("firm_id IS NOT NULL") +    accounts = Account.limit(4)      assert_equal 3, accounts.count(:firm_id)      assert_equal 3, accounts.select(:firm_id).count +    assert_equal 3, accounts.select("firm_id firm_id").count    end    def test_limit_should_apply_before_count_arel_attribute -    accounts = Account.limit(3).where("firm_id IS NOT NULL") +    accounts = Account.limit(4)      firm_id_attribute = Account.arel_table[:firm_id]      assert_equal 3, accounts.count(firm_id_attribute) @@ -421,10 +422,6 @@ class CalculationsTest < ActiveRecord::TestCase    def test_count_with_distinct      assert_equal 4, Account.select(:credit_limit).distinct.count - -    assert_deprecated do -      assert_equal 4, Account.select(:credit_limit).uniq.count -    end    end    def test_count_with_aliased_attribute diff --git a/activerecord/test/cases/callbacks_test.rb b/activerecord/test/cases/callbacks_test.rb index 4b517e9d70..53ff037de1 100644 --- a/activerecord/test/cases/callbacks_test.rb +++ b/activerecord/test/cases/callbacks_test.rb @@ -125,11 +125,11 @@ class ContextualCallbacksDeveloper < ActiveRecord::Base    after_validation :after_validation_on_create_and_update, on: [ :create, :update ]    def before_validation_on_create_and_update -    history << "before_validation_on_#{self.validation_context}".to_sym +    history << "before_validation_on_#{validation_context}".to_sym    end    def after_validation_on_create_and_update -    history << "after_validation_on_#{self.validation_context}".to_sym +    history << "after_validation_on_#{validation_context}".to_sym    end    def history @@ -449,7 +449,7 @@ class CallbacksTest < ActiveRecord::TestCase        assert david.valid?        assert !david.save        exc = assert_raise(ActiveRecord::RecordNotSaved) { david.save! } -      assert_equal exc.record, david +      assert_equal david, exc.record        assert_equal "Failed to save the record", exc.message      end @@ -493,7 +493,7 @@ class CallbacksTest < ActiveRecord::TestCase      assert_deprecated do        assert !david.destroy        exc = assert_raise(ActiveRecord::RecordNotDestroyed) { david.destroy! } -      assert_equal exc.record, david +      assert_equal david, exc.record        assert_equal "Failed to destroy the record", exc.message      end      assert_not_nil ImmutableDeveloper.find_by_id(1) @@ -527,7 +527,7 @@ class CallbacksTest < ActiveRecord::TestCase      assert david.valid?      assert !david.save      exc = assert_raise(ActiveRecord::RecordNotSaved) { david.save! } -    assert_equal exc.record, david +    assert_equal david, exc.record      david = DeveloperWithCanceledCallbacks.find(1)      david.salary = 10_000_000 @@ -554,7 +554,7 @@ class CallbacksTest < ActiveRecord::TestCase      david = DeveloperWithCanceledCallbacks.find(1)      assert !david.destroy      exc = assert_raise(ActiveRecord::RecordNotDestroyed) { david.destroy! } -    assert_equal exc.record, david +    assert_equal david, exc.record      assert_not_nil ImmutableDeveloper.find_by_id(1)      someone = CallbackHaltedDeveloper.find(1) diff --git a/activerecord/test/cases/coders/yaml_column_test.rb b/activerecord/test/cases/coders/yaml_column_test.rb index b9c6224425..59ef389326 100644 --- a/activerecord/test/cases/coders/yaml_column_test.rb +++ b/activerecord/test/cases/coders/yaml_column_test.rb @@ -5,46 +5,48 @@ module ActiveRecord    module Coders      class YAMLColumnTest < ActiveRecord::TestCase        def test_initialize_takes_class -        coder = YAMLColumn.new(Object) +        coder = YAMLColumn.new("attr_name", Object)          assert_equal Object, coder.object_class        end        def test_type_mismatch_on_different_classes_on_dump -        coder = YAMLColumn.new(Array) -        assert_raises(SerializationTypeMismatch) do +        coder = YAMLColumn.new("tags", Array) +        error = assert_raises(SerializationTypeMismatch) do            coder.dump("a")          end +        assert_equal %{can't dump `tags`: was supposed to be a Array, but was a String. -- "a"}, error.to_s        end        def test_type_mismatch_on_different_classes -        coder = YAMLColumn.new(Array) -        assert_raises(SerializationTypeMismatch) do +        coder = YAMLColumn.new("tags", Array) +        error = assert_raises(SerializationTypeMismatch) do            coder.load "--- foo"          end +        assert_equal %{can't load `tags`: was supposed to be a Array, but was a String. -- "foo"}, error.to_s        end        def test_nil_is_ok -        coder = YAMLColumn.new +        coder = YAMLColumn.new("attr_name")          assert_nil coder.load "--- "        end        def test_returns_new_with_different_class -        coder = YAMLColumn.new SerializationTypeMismatch +        coder = YAMLColumn.new("attr_name", SerializationTypeMismatch)          assert_equal SerializationTypeMismatch, coder.load("--- ").class        end        def test_returns_string_unless_starts_with_dash -        coder = YAMLColumn.new +        coder = YAMLColumn.new("attr_name")          assert_equal "foo", coder.load("foo")        end        def test_load_handles_other_classes -        coder = YAMLColumn.new +        coder = YAMLColumn.new("attr_name")          assert_equal [], coder.load([])        end        def test_load_doesnt_swallow_yaml_exceptions -        coder = YAMLColumn.new +        coder = YAMLColumn.new("attr_name")          bad_yaml = "--- {"          assert_raises(Psych::SyntaxError) do            coder.load(bad_yaml) @@ -52,7 +54,7 @@ module ActiveRecord        end        def test_load_doesnt_handle_undefined_class_or_module -        coder = YAMLColumn.new +        coder = YAMLColumn.new("attr_name")          missing_class_yaml = '--- !ruby/object:DoesNotExistAndShouldntEver {}\n'          assert_raises(ArgumentError) do            coder.load(missing_class_yaml) diff --git a/activerecord/test/cases/column_definition_test.rb b/activerecord/test/cases/column_definition_test.rb index a65bb89052..0dffe5e60b 100644 --- a/activerecord/test/cases/column_definition_test.rb +++ b/activerecord/test/cases/column_definition_test.rb @@ -37,48 +37,6 @@ module ActiveRecord            column.limit, column.precision, column.scale, column.default, column.null)          assert_equal "title varchar(20) DEFAULT 'Hello' NOT NULL", @viz.accept(column_def)        end - -      if current_adapter?(:Mysql2Adapter) -        def test_should_set_default_for_mysql_binary_data_types -          type = SqlTypeMetadata.new(type: :binary, sql_type: "binary(1)") -          binary_column = MySQL::Column.new("title", "a", type) -          assert_equal "a", binary_column.default - -          type = SqlTypeMetadata.new(type: :binary, sql_type: "varbinary") -          varbinary_column = MySQL::Column.new("title", "a", type) -          assert_equal "a", varbinary_column.default -        end - -        def test_should_be_empty_string_default_for_mysql_binary_data_types -          type = SqlTypeMetadata.new(type: :binary, sql_type: "binary(1)") -          binary_column = MySQL::Column.new("title", "", type, false) -          assert_equal "", binary_column.default - -          type = SqlTypeMetadata.new(type: :binary, sql_type: "varbinary") -          varbinary_column = MySQL::Column.new("title", "", type, false) -          assert_equal "", varbinary_column.default -        end - -        def test_should_not_set_default_for_blob_and_text_data_types -          text_type = MySQL::TypeMetadata.new(SqlTypeMetadata.new(type: :text)) - -          text_column = MySQL::Column.new("title", nil, text_type) -          assert_nil text_column.default - -          not_null_text_column = MySQL::Column.new("title", nil, text_type, false) -          assert_nil not_null_text_column.default -        end - -        def test_has_default_should_return_false_for_blob_and_text_data_types -          binary_type = SqlTypeMetadata.new(sql_type: "blob") -          blob_column = MySQL::Column.new("title", nil, binary_type) -          assert !blob_column.has_default? - -          text_type = SqlTypeMetadata.new(type: :text) -          text_column = MySQL::Column.new("title", nil, text_type) -          assert !text_column.has_default? -        end -      end      end    end  end diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb index 2c33bf22ab..4f2392042b 100644 --- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb +++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb @@ -89,6 +89,41 @@ module ActiveRecord            rd.close          end +        def test_pool_from_any_process_for_uses_most_recent_spec +          skip unless current_adapter?(:SQLite3Adapter) + +          file = Tempfile.new "lol.sqlite3" + +          rd, wr = IO.pipe +          rd.binmode +          wr.binmode + +          pid = fork do +            ActiveRecord::Base.configurations["arunit"]["database"] = file.path +            ActiveRecord::Base.establish_connection(:arunit) + +            pid2 = fork do +              wr.write ActiveRecord::Base.connection_config[:database] +              wr.close +            end + +            Process.waitpid pid2 +          end + +          Process.waitpid pid + +          wr.close + +          assert_equal file.path, rd.read + +          rd.close +        ensure +          if file +            file.close +            file.unlink +          end +        end +          def test_a_class_using_custom_pool_and_switching_back_to_primary            klass2 = Class.new(Base) { def self.name; "klass2"; end } diff --git a/activerecord/test/cases/connection_adapters/schema_cache_test.rb b/activerecord/test/cases/connection_adapters/schema_cache_test.rb index 1b4f80fc67..106323ccc9 100644 --- a/activerecord/test/cases/connection_adapters/schema_cache_test.rb +++ b/activerecord/test/cases/connection_adapters/schema_cache_test.rb @@ -80,10 +80,13 @@ module ActiveRecord          end        end -      def test_table_methods_deprecation -        assert_deprecated { assert @cache.table_exists?("posts") } -        assert_deprecated { assert @cache.tables("posts") } -        assert_deprecated { @cache.clear_table_cache!("posts") } +      def test_data_source_exist +        assert @cache.data_source_exists?("posts") +        assert_not @cache.data_source_exists?("foo") +      end + +      def test_clear_data_source_cache +        @cache.clear_data_source_cache!("posts")        end        private diff --git a/activerecord/test/cases/connection_adapters/type_lookup_test.rb b/activerecord/test/cases/connection_adapters/type_lookup_test.rb index e2e5445a4e..a348c2d783 100644 --- a/activerecord/test/cases/connection_adapters/type_lookup_test.rb +++ b/activerecord/test/cases/connection_adapters/type_lookup_test.rb @@ -89,12 +89,20 @@ unless current_adapter?(:PostgreSQLAdapter) # PostgreSQL does not use type strin          end          def test_decimal_without_scale -          types = %w{decimal(2) decimal(2,0) numeric(2) numeric(2,0) number(2) number(2,0)} -          types.each do |type| -            cast_type = @connection.type_map.lookup(type) - -            assert_equal :decimal, cast_type.type -            assert_equal 2, cast_type.cast(2.1) +          if current_adapter?(:OracleAdapter) +            { +              decimal: %w{decimal(2) decimal(2,0) numeric(2) numeric(2,0)}, +              integer: %w{number(2) number(2,0)} +            } +          else +            { decimal: %w{decimal(2) decimal(2,0) numeric(2) numeric(2,0) number(2) number(2,0)} } +          end.each do |expected_type, types| +            types.each do |type| +              cast_type = @connection.type_map.lookup(type) + +              assert_equal expected_type, cast_type.type +              assert_equal 2, cast_type.cast(2.1) +            end            end          end diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb index 1d4cd3c78b..afd0ac2dd4 100644 --- a/activerecord/test/cases/connection_pool_test.rb +++ b/activerecord/test/cases/connection_pool_test.rb @@ -341,14 +341,18 @@ module ActiveRecord          end        end +      class ConnectionTestModel < ActiveRecord::Base +      end +        def test_connection_notification_is_called          payloads = []          subscription = ActiveSupport::Notifications.subscribe("!connection.active_record") do |name, started, finished, unique_id, payload|            payloads << payload          end -        ActiveRecord::Base.establish_connection :arunit +        ConnectionTestModel.establish_connection :arunit +          assert_equal [:config, :connection_id, :spec_name], payloads[0].keys.sort -        assert_equal "primary", payloads[0][:spec_name] +        assert_equal "ActiveRecord::ConnectionAdapters::ConnectionPoolTest::ConnectionTestModel", payloads[0][:spec_name]        ensure          ActiveSupport::Notifications.unsubscribe(subscription) if subscription        end @@ -395,7 +399,7 @@ module ActiveRecord              all_threads_in_new_connection.wait            end          rescue Timeout::Error -          flunk "pool unable to establish connections concurrently or implementation has " << +          flunk "pool unable to establish connections concurrently or implementation has " \                  "changed, this test then needs to patch a different :new_connection method"          ensure            # clean up the threads @@ -437,7 +441,7 @@ module ActiveRecord          end        end -      def test_bang_versions_of_disconnect_and_clear_reloadable_connections_if_unable_to_aquire_all_connections_proceed_anyway +      def test_bang_versions_of_disconnect_and_clear_reloadable_connections_if_unable_to_acquire_all_connections_proceed_anyway          @pool.checkout_timeout = 0.001 # no need to delay test suite by waiting the whole full default timeout          [:disconnect!, :clear_reloadable_connections!].each do |group_action_method|            @pool.with_connection do |connection| @@ -501,11 +505,11 @@ module ActiveRecord                  first_thread.join(2)                  second_thread.join(2) -                puts '---' +                puts "---"                  p [first_thread, second_thread]                  p pool.stat                  p pool.connections.map(&:owner) -                puts '<<<' +                puts "<<<"                  puts                end diff --git a/activerecord/test/cases/counter_cache_test.rb b/activerecord/test/cases/counter_cache_test.rb index 84f2c3a465..46d7526cc0 100644 --- a/activerecord/test/cases/counter_cache_test.rb +++ b/activerecord/test/cases/counter_cache_test.rb @@ -211,4 +211,155 @@ class CounterCacheTest < ActiveRecord::TestCase        aircraft.wheels.first.destroy      end    end + +  test "update counters doesn't touch timestamps by default" do +    @topic.update_column :updated_at, 5.minutes.ago +    previously_updated_at = @topic.updated_at + +    Topic.update_counters(@topic.id, replies_count: -1) + +    assert_equal previously_updated_at, @topic.updated_at +  end + +  test "update counters doesn't touch timestamps with touch: []" do +    @topic.update_column :updated_at, 5.minutes.ago +    previously_updated_at = @topic.updated_at + +    Topic.update_counters(@topic.id, replies_count: -1, touch: []) + +    assert_equal previously_updated_at, @topic.updated_at +  end + +  test "update counters with touch: true" do +    assert_touching @topic, :updated_at do +      Topic.update_counters(@topic.id, replies_count: -1, touch: true) +    end +  end + +  test "update counters of multiple records with touch: true" do +    t1, t2 = topics(:first, :second) + +    assert_touching t1, :updated_at do +      assert_difference ["t1.reload.replies_count", "t2.reload.replies_count"], 2 do +        Topic.update_counters([t1.id, t2.id], replies_count: 2, touch: true) +      end +    end +  end + +  test "update multiple counters with touch: true" do +    assert_touching @topic, :updated_at do +      Topic.update_counters(@topic.id, replies_count: 2, unique_replies_count: 2, touch: true) +    end +  end + +  test "reset counters with touch: true" do +    assert_touching @topic, :updated_at do +      Topic.reset_counters(@topic.id, :replies, touch: true) +    end +  end + +  test "reset multiple counters with touch: true" do +    assert_touching @topic, :updated_at do +      Topic.update_counters(@topic.id, replies_count: 1, unique_replies_count: 1) +      Topic.reset_counters(@topic.id, :replies, :unique_replies, touch: true) +    end +  end + +  test "increment counters with touch: true" do +    assert_touching @topic, :updated_at do +      Topic.increment_counter(:replies_count, @topic.id, touch: true) +    end +  end + +  test "decrement counters with touch: true" do +    assert_touching @topic, :updated_at do +      Topic.decrement_counter(:replies_count, @topic.id, touch: true) +    end +  end + +  test "update counters with touch: :written_on" do +    assert_touching @topic, :written_on do +      Topic.update_counters(@topic.id, replies_count: -1, touch: :written_on) +    end +  end + +  test "update multiple counters with touch: :written_on" do +    assert_touching @topic, :written_on do +      Topic.update_counters(@topic.id, replies_count: 2, unique_replies_count: 2, touch: :written_on) +    end +  end + +  test "reset counters with touch: :written_on" do +    assert_touching @topic, :written_on do +      Topic.reset_counters(@topic.id, :replies, touch: :written_on) +    end +  end + +  test "reset multiple counters with touch: :written_on" do +    assert_touching @topic, :written_on do +      Topic.update_counters(@topic.id, replies_count: 1, unique_replies_count: 1) +      Topic.reset_counters(@topic.id, :replies, :unique_replies, touch: :written_on) +    end +  end + +  test "increment counters with touch: :written_on" do +    assert_touching @topic, :written_on do +      Topic.increment_counter(:replies_count, @topic.id, touch: :written_on) +    end +  end + +  test "decrement counters with touch: :written_on" do +    assert_touching @topic, :written_on do +      Topic.decrement_counter(:replies_count, @topic.id, touch: :written_on) +    end +  end + +  test "update counters with touch: %i( updated_at written_on )" do +    assert_touching @topic, :updated_at, :written_on do +      Topic.update_counters(@topic.id, replies_count: -1, touch: %i( updated_at written_on )) +    end +  end + +  test "update multiple counters with touch: %i( updated_at written_on )" do +    assert_touching @topic, :updated_at, :written_on do +      Topic.update_counters(@topic.id, replies_count: 2, unique_replies_count: 2, touch: %i( updated_at written_on )) +    end +  end + +  test "reset counters with touch: %i( updated_at written_on )" do +    assert_touching @topic, :updated_at, :written_on do +      Topic.reset_counters(@topic.id, :replies, touch: %i( updated_at written_on )) +    end +  end + +  test "reset multiple counters with touch: %i( updated_at written_on )" do +    assert_touching @topic, :updated_at, :written_on do +      Topic.update_counters(@topic.id, replies_count: 1, unique_replies_count: 1) +      Topic.reset_counters(@topic.id, :replies, :unique_replies, touch: %i( updated_at written_on )) +    end +  end + +  test "increment counters with touch: %i( updated_at written_on )" do +    assert_touching @topic, :updated_at, :written_on do +      Topic.increment_counter(:replies_count, @topic.id, touch: %i( updated_at written_on )) +    end +  end + +  test "decrement counters with touch: %i( updated_at written_on )" do +    assert_touching @topic, :updated_at, :written_on do +      Topic.decrement_counter(:replies_count, @topic.id, touch: %i( updated_at written_on )) +    end +  end + +  private +    def assert_touching(record, *attributes) +      record.update_columns attributes.map { |attr| [ attr, 5.minutes.ago ] }.to_h +      touch_times = attributes.map { |attr| [ attr, record.public_send(attr) ] }.to_h + +      yield + +      touch_times.each do |attr, previous_touch_time| +        assert_operator previous_touch_time, :<, record.reload.public_send(attr) +      end +    end  end diff --git a/activerecord/test/cases/database_statements_test.rb b/activerecord/test/cases/database_statements_test.rb index bb16076fd2..66035865be 100644 --- a/activerecord/test/cases/database_statements_test.rb +++ b/activerecord/test/cases/database_statements_test.rb @@ -20,12 +20,6 @@ class DatabaseStatementsTest < ActiveRecord::TestCase      assert_not_nil return_the_inserted_id(method: :create)    end -  def test_insert_update_delete_sql_is_deprecated -    assert_deprecated { @connection.insert_sql("INSERT INTO accounts (firm_id,credit_limit) VALUES (42,5000)") } -    assert_deprecated { @connection.update_sql("UPDATE accounts SET credit_limit = 6000 WHERE firm_id = 42") } -    assert_deprecated { @connection.delete_sql("DELETE FROM accounts WHERE firm_id = 42") } -  end -    private      def return_the_inserted_id(method:) diff --git a/activerecord/test/cases/date_time_precision_test.rb b/activerecord/test/cases/date_time_precision_test.rb index a1c3c5af9c..e4a2f9ee17 100644 --- a/activerecord/test/cases/date_time_precision_test.rb +++ b/activerecord/test/cases/date_time_precision_test.rb @@ -73,7 +73,7 @@ if subsecond_precision_supported?        assert_match %r{t\.datetime\s+"updated_at",\s+precision: 6,\s+null: false$}, output      end -    if current_adapter?(:PostgreSQLAdapter) +    if current_adapter?(:PostgreSQLAdapter, :SQLServerAdapter)        def test_datetime_precision_with_zero_should_be_dumped          @connection.create_table(:foos, force: true) do |t|            t.timestamps precision: 0 diff --git a/activerecord/test/cases/errors_test.rb b/activerecord/test/cases/errors_test.rb index 0711a372f2..73feb831d0 100644 --- a/activerecord/test/cases/errors_test.rb +++ b/activerecord/test/cases/errors_test.rb @@ -5,7 +5,7 @@ class ErrorsTest < ActiveRecord::TestCase      base = ActiveRecord::ActiveRecordError      error_klasses = ObjectSpace.each_object(Class).select { |klass| klass < base } -    error_klasses.each do |error_klass| +    (error_klasses - [ActiveRecord::AmbiguousSourceReflectionForThroughAssociation]).each do |error_klass|        begin          error_klass.new.inspect        rescue ArgumentError diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index d6d24004fe..deec669935 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -117,8 +117,8 @@ class FinderTest < ActiveRecord::TestCase      assert_equal "The Fourth Topic of the day", records[2].title    end -  def test_find_passing_active_record_object_is_deprecated -    assert_deprecated do +  def test_find_passing_active_record_object_is_not_permitted +    assert_raises(ArgumentError) do        Topic.find(Topic.last)      end    end @@ -167,8 +167,8 @@ class FinderTest < ActiveRecord::TestCase      assert_equal false, relation.exists?(false)    end -  def test_exists_passing_active_record_object_is_deprecated -    assert_deprecated do +  def test_exists_passing_active_record_object_is_not_permitted +    assert_raises(ArgumentError) do        Topic.exists?(Topic.new)      end    end @@ -339,6 +339,11 @@ class FinderTest < ActiveRecord::TestCase      assert_equal author.post, Post.find_by(author_id: Author.where(id: author))    end +  def test_find_by_and_where_consistency_with_active_record_instance +    author = authors(:david) +    assert_equal Post.where(author_id: author).take, Post.find_by(author_id: author) +  end +    def test_take      assert_equal topics(:first), Topic.take    end @@ -592,7 +597,7 @@ class FinderTest < ActiveRecord::TestCase    end    def test_last_with_irreversible_order -    assert_deprecated do +    assert_raises(ActiveRecord::IrreversibleOrderError) do        Topic.order("coalesce(author_name, title)").last      end    end @@ -858,13 +863,13 @@ class FinderTest < ActiveRecord::TestCase    end    def test_bind_variables_with_quotes -    Company.create("name" => "37signals' go'es agains") -    assert Company.where(["name = ?", "37signals' go'es agains"]).first +    Company.create("name" => "37signals' go'es against") +    assert Company.where(["name = ?", "37signals' go'es against"]).first    end    def test_named_bind_variables_with_quotes -    Company.create("name" => "37signals' go'es agains") -    assert Company.where(["name = :name", { name: "37signals' go'es agains" }]).first +    Company.create("name" => "37signals' go'es against") +    assert Company.where(["name = :name", { name: "37signals' go'es against" }]).first    end    def test_named_bind_variables diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index dd48053823..61e596e208 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -7,6 +7,7 @@ require "models/binary"  require "models/book"  require "models/bulb"  require "models/category" +require "models/post"  require "models/comment"  require "models/company"  require "models/computer" @@ -19,7 +20,6 @@ require "models/matey"  require "models/other_dog"  require "models/parrot"  require "models/pirate" -require "models/post"  require "models/randomly_named_c1"  require "models/reply"  require "models/ship" @@ -1012,7 +1012,7 @@ end  class FixtureClassNamesTest < ActiveRecord::TestCase    def setup -    @saved_cache = self.fixture_class_names.dup +    @saved_cache = fixture_class_names.dup    end    def teardown diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index f1d69a215a..1ddcbf0e4f 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -27,9 +27,6 @@ ARTest.connect  # Quote "type" if it's a reserved word for the current connection.  QUOTED_TYPE = ActiveRecord::Base.connection.quote_column_name("type") -# FIXME: Remove this when the deprecation cycle on TZ aware types by default ends. -ActiveRecord::Base.time_zone_aware_types << :time -  def current_adapter?(*types)    types.any? do |type|      ActiveRecord::ConnectionAdapters.const_defined?(type) && diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb index 9ad4664567..e570e9ac1d 100644 --- a/activerecord/test/cases/inheritance_test.rb +++ b/activerecord/test/cases/inheritance_test.rb @@ -58,21 +58,21 @@ class InheritanceTest < ActiveRecord::TestCase    end    def test_compute_type_success -    assert_equal Author, ActiveRecord::Base.send(:compute_type, "Author") +    assert_equal Author, Company.send(:compute_type, "Author")    end    def test_compute_type_nonexistent_constant      e = assert_raises NameError do -      ActiveRecord::Base.send :compute_type, "NonexistentModel" +      Company.send :compute_type, "NonexistentModel"      end -    assert_equal "uninitialized constant ActiveRecord::Base::NonexistentModel", e.message -    assert_equal "ActiveRecord::Base::NonexistentModel", e.name +    assert_equal "uninitialized constant Company::NonexistentModel", e.message +    assert_equal "Company::NonexistentModel", e.name    end    def test_compute_type_no_method_error      ActiveSupport::Dependencies.stub(:safe_constantize, proc { raise NoMethodError }) do        assert_raises NoMethodError do -        ActiveRecord::Base.send :compute_type, "InvalidModel" +        Company.send :compute_type, "InvalidModel"        end      end    end @@ -90,7 +90,7 @@ class InheritanceTest < ActiveRecord::TestCase      ActiveSupport::Dependencies.stub(:safe_constantize, proc { raise e }) do        exception = assert_raises NameError do -        ActiveRecord::Base.send :compute_type, "InvalidModel" +        Company.send :compute_type, "InvalidModel"        end        assert_equal error.message, exception.message      end @@ -99,7 +99,7 @@ class InheritanceTest < ActiveRecord::TestCase    def test_compute_type_argument_error      ActiveSupport::Dependencies.stub(:safe_constantize, proc { raise ArgumentError }) do        assert_raises ArgumentError do -        ActiveRecord::Base.send :compute_type, "InvalidModel" +        Company.send :compute_type, "InvalidModel"        end      end    end diff --git a/activerecord/test/cases/invertible_migration_test.rb b/activerecord/test/cases/invertible_migration_test.rb index 9d5aace7db..cc3951e2ba 100644 --- a/activerecord/test/cases/invertible_migration_test.rb +++ b/activerecord/test/cases/invertible_migration_test.rb @@ -165,10 +165,8 @@ module ActiveRecord      teardown do        %w[horses new_horses].each do |table| -        ActiveSupport::Deprecation.silence do -          if ActiveRecord::Base.connection.table_exists?(table) -            ActiveRecord::Base.connection.drop_table(table) -          end +        if ActiveRecord::Base.connection.table_exists?(table) +          ActiveRecord::Base.connection.drop_table(table)          end        end        ActiveRecord::Migration.verbose = @verbose_was @@ -199,14 +197,14 @@ module ActiveRecord      def test_migrate_up        migration = InvertibleMigration.new        migration.migrate(:up) -      ActiveSupport::Deprecation.silence { assert migration.connection.table_exists?("horses"), "horses should exist" } +      assert migration.connection.table_exists?("horses"), "horses should exist"      end      def test_migrate_down        migration = InvertibleMigration.new        migration.migrate :up        migration.migrate :down -      ActiveSupport::Deprecation.silence { assert !migration.connection.table_exists?("horses") } +      assert !migration.connection.table_exists?("horses")      end      def test_migrate_revert @@ -214,11 +212,11 @@ module ActiveRecord        revert = InvertibleRevertMigration.new        migration.migrate :up        revert.migrate :up -      ActiveSupport::Deprecation.silence { assert !migration.connection.table_exists?("horses") } +      assert !migration.connection.table_exists?("horses")        revert.migrate :down -      ActiveSupport::Deprecation.silence { assert migration.connection.table_exists?("horses") } +      assert migration.connection.table_exists?("horses")        migration.migrate :down -      ActiveSupport::Deprecation.silence { assert !migration.connection.table_exists?("horses") } +      assert !migration.connection.table_exists?("horses")      end      def test_migrate_revert_by_part @@ -226,24 +224,18 @@ module ActiveRecord        received = []        migration = InvertibleByPartsMigration.new        migration.test = ->(dir) { -        ActiveSupport::Deprecation.silence do -          assert migration.connection.table_exists?("horses") -          assert migration.connection.table_exists?("new_horses") -        end +        assert migration.connection.table_exists?("horses") +        assert migration.connection.table_exists?("new_horses")          received << dir        }        migration.migrate :up        assert_equal [:both, :up], received -      ActiveSupport::Deprecation.silence do -        assert !migration.connection.table_exists?("horses") -        assert migration.connection.table_exists?("new_horses") -      end +      assert !migration.connection.table_exists?("horses") +      assert migration.connection.table_exists?("new_horses")        migration.migrate :down        assert_equal [:both, :up, :both, :down], received -      ActiveSupport::Deprecation.silence do -        assert migration.connection.table_exists?("horses") -        assert !migration.connection.table_exists?("new_horses") -      end +      assert migration.connection.table_exists?("horses") +      assert !migration.connection.table_exists?("new_horses")      end      def test_migrate_revert_whole_migration @@ -252,20 +244,20 @@ module ActiveRecord          revert = RevertWholeMigration.new(klass)          migration.migrate :up          revert.migrate :up -        ActiveSupport::Deprecation.silence { assert !migration.connection.table_exists?("horses") } +        assert !migration.connection.table_exists?("horses")          revert.migrate :down -        ActiveSupport::Deprecation.silence { assert migration.connection.table_exists?("horses") } +        assert migration.connection.table_exists?("horses")          migration.migrate :down -        ActiveSupport::Deprecation.silence { assert !migration.connection.table_exists?("horses") } +        assert !migration.connection.table_exists?("horses")        end      end      def test_migrate_nested_revert_whole_migration        revert = NestedRevertWholeMigration.new(InvertibleRevertMigration)        revert.migrate :down -      ActiveSupport::Deprecation.silence { assert revert.connection.table_exists?("horses") } +      assert revert.connection.table_exists?("horses")        revert.migrate :up -      ActiveSupport::Deprecation.silence { assert !revert.connection.table_exists?("horses") } +      assert !revert.connection.table_exists?("horses")      end      def test_migrate_revert_change_column_default @@ -330,24 +322,24 @@ module ActiveRecord      def test_legacy_up        LegacyMigration.migrate :up -      ActiveSupport::Deprecation.silence { assert ActiveRecord::Base.connection.table_exists?("horses"), "horses should exist" } +      assert ActiveRecord::Base.connection.table_exists?("horses"), "horses should exist"      end      def test_legacy_down        LegacyMigration.migrate :up        LegacyMigration.migrate :down -      ActiveSupport::Deprecation.silence { assert !ActiveRecord::Base.connection.table_exists?("horses"), "horses should not exist" } +      assert !ActiveRecord::Base.connection.table_exists?("horses"), "horses should not exist"      end      def test_up        LegacyMigration.up -      ActiveSupport::Deprecation.silence { assert ActiveRecord::Base.connection.table_exists?("horses"), "horses should exist" } +      assert ActiveRecord::Base.connection.table_exists?("horses"), "horses should exist"      end      def test_down        LegacyMigration.up        LegacyMigration.down -      ActiveSupport::Deprecation.silence { assert !ActiveRecord::Base.connection.table_exists?("horses"), "horses should not exist" } +      assert !ActiveRecord::Base.connection.table_exists?("horses"), "horses should not exist"      end      def test_migrate_down_with_table_name_prefix @@ -356,7 +348,7 @@ module ActiveRecord        migration = InvertibleMigration.new        migration.migrate(:up)        assert_nothing_raised { migration.migrate(:down) } -      ActiveSupport::Deprecation.silence { assert !ActiveRecord::Base.connection.table_exists?("p_horses_s"), "p_horses_s should not exist" } +      assert !ActiveRecord::Base.connection.table_exists?("p_horses_s"), "p_horses_s should not exist"      ensure        ActiveRecord::Base.table_name_prefix = ActiveRecord::Base.table_name_suffix = ""      end diff --git a/activerecord/test/cases/json_serialization_test.rb b/activerecord/test/cases/json_serialization_test.rb index a2150483f3..155e858822 100644 --- a/activerecord/test/cases/json_serialization_test.rb +++ b/activerecord/test/cases/json_serialization_test.rb @@ -243,7 +243,7 @@ class DatabaseConnectedJsonEncodingTest < ActiveRecord::TestCase      assert !@david.posts.first.respond_to?(:favorite_quote)      assert_match %r{"favorite_quote":"Constraints are liberating"}, json -    assert_equal %r{"favorite_quote":}.match(json).size, 1 +    assert_equal 1, %r{"favorite_quote":}.match(json).size    end    def test_should_allow_only_option_for_list_of_authors diff --git a/activerecord/test/cases/locking_test.rb b/activerecord/test/cases/locking_test.rb index 95fb670dac..9e42242e55 100644 --- a/activerecord/test/cases/locking_test.rb +++ b/activerecord/test/cases/locking_test.rb @@ -18,7 +18,7 @@ class LockWithoutDefault < ActiveRecord::Base; end  class LockWithCustomColumnWithoutDefault < ActiveRecord::Base    self.table_name = :lock_without_defaults_cust -  self.column_defaults # to test @column_defaults caching. +  column_defaults # to test @column_defaults caching.    self.locking_column = :custom_lock_version  end diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb index 03f9c4a9ed..48cfe89882 100644 --- a/activerecord/test/cases/migration/change_schema_test.rb +++ b/activerecord/test/cases/migration/change_schema_test.rb @@ -409,9 +409,9 @@ module ActiveRecord        def test_drop_table_if_exists          connection.create_table(:testings) -        ActiveSupport::Deprecation.silence { assert connection.table_exists?(:testings) } +        assert connection.table_exists?(:testings)          connection.drop_table(:testings, if_exists: true) -        ActiveSupport::Deprecation.silence { assert_not connection.table_exists?(:testings) } +        assert_not connection.table_exists?(:testings)        end        def test_drop_table_if_exists_nothing_raised diff --git a/activerecord/test/cases/migration/change_table_test.rb b/activerecord/test/cases/migration/change_table_test.rb index 8a4242cf1d..ec817a579b 100644 --- a/activerecord/test/cases/migration/change_table_test.rb +++ b/activerecord/test/cases/migration/change_table_test.rb @@ -101,12 +101,7 @@ module ActiveRecord        def test_primary_key_creates_primary_key_column          with_change_table do |t| -          if current_adapter?(:Mysql2Adapter) -            @connection.expect :add_column, nil, [:delete_me, :id, :primary_key, { first: true, auto_increment: true, limit: 8, primary_key: true }] -          else -            @connection.expect :add_column, nil, [:delete_me, :id, :primary_key, primary_key: true, first: true] -          end - +          @connection.expect :add_column, nil, [:delete_me, :id, :primary_key, primary_key: true, first: true]            t.primary_key :id, first: true          end        end diff --git a/activerecord/test/cases/migration/compatibility_test.rb b/activerecord/test/cases/migration/compatibility_test.rb index 0a4b604601..fc3ec47825 100644 --- a/activerecord/test/cases/migration/compatibility_test.rb +++ b/activerecord/test/cases/migration/compatibility_test.rb @@ -1,4 +1,5 @@  require "cases/helper" +require "support/schema_dumping_helper"  module ActiveRecord    class Migration @@ -55,7 +56,7 @@ module ActiveRecord        end        def test_references_does_not_add_index_by_default -        migration = Class.new(ActiveRecord::Migration) { +        migration = Class.new(ActiveRecord::Migration[4.2]) {            def migrate(x)              create_table :more_testings do |t|                t.references :foo @@ -73,7 +74,7 @@ module ActiveRecord        end        def test_timestamps_have_null_constraints_if_not_present_in_migration_of_create_table -        migration = Class.new(ActiveRecord::Migration) { +        migration = Class.new(ActiveRecord::Migration[4.2]) {            def migrate(x)              create_table :more_testings do |t|                t.timestamps @@ -90,7 +91,7 @@ module ActiveRecord        end        def test_timestamps_have_null_constraints_if_not_present_in_migration_for_adding_timestamps_to_existing_table -        migration = Class.new(ActiveRecord::Migration) { +        migration = Class.new(ActiveRecord::Migration[4.2]) {            def migrate(x)              add_timestamps :testings            end @@ -102,17 +103,115 @@ module ActiveRecord          assert connection.columns(:testings).find { |c| c.name == "updated_at" }.null        end -      def test_legacy_migrations_get_deprecation_warning_when_run -        migration = Class.new(ActiveRecord::Migration) { -          def up -            add_column :testings, :baz, :string -          end -        } +      def test_legacy_migrations_raises_exception_when_inherited +        e = assert_raises(StandardError) do +          class_eval("class LegacyMigration < ActiveRecord::Migration; end") +        end +        assert_match(/LegacyMigration < ActiveRecord::Migration\[4\.2\]/, e.message) +      end +    end +  end +end + +class LegacyPrimaryKeyTest < ActiveRecord::TestCase +  include SchemaDumpingHelper + +  self.use_transactional_tests = false + +  class LegacyPrimaryKey < ActiveRecord::Base +  end + +  def setup +    @migration = nil +    @verbose_was = ActiveRecord::Migration.verbose +    ActiveRecord::Migration.verbose = false +  end + +  def teardown +    @migration.migrate(:down) if @migration +    ActiveRecord::Migration.verbose = @verbose_was +    ActiveRecord::SchemaMigration.delete_all rescue nil +    LegacyPrimaryKey.reset_column_information +  end + +  def test_legacy_primary_key_should_be_auto_incremented +    @migration = Class.new(ActiveRecord::Migration[5.0]) { +      def change +        create_table :legacy_primary_keys do |t| +        end +      end +    }.new + +    @migration.migrate(:up) + +    legacy_pk = LegacyPrimaryKey.columns_hash["id"] +    assert_not legacy_pk.bigint? +    assert_not legacy_pk.null -        assert_deprecated do -          migration.migrate :up +    record1 = LegacyPrimaryKey.create! +    assert_not_nil record1.id + +    record1.destroy + +    record2 = LegacyPrimaryKey.create! +    assert_not_nil record2.id +    assert_operator record2.id, :>, record1.id +  end + +  def test_legacy_integer_primary_key_should_not_be_auto_incremented +    skip if current_adapter?(:SQLite3Adapter) + +    @migration = Class.new(ActiveRecord::Migration[5.0]) { +      def change +        create_table :legacy_primary_keys, id: :integer do |t| +        end +      end +    }.new + +    @migration.migrate(:up) + +    assert_raises(ActiveRecord::NotNullViolation) do +      LegacyPrimaryKey.create! +    end + +    schema = dump_table_schema "legacy_primary_keys" +    assert_match %r{create_table "legacy_primary_keys", id: :integer, default: nil}, schema +  end + +  if current_adapter?(:Mysql2Adapter) +    def test_legacy_bigint_primary_key_should_be_auto_incremented +      @migration = Class.new(ActiveRecord::Migration[5.0]) { +        def change +          create_table :legacy_primary_keys, id: :bigint          end +      }.new + +      @migration.migrate(:up) + +      legacy_pk = LegacyPrimaryKey.columns_hash["id"] +      assert legacy_pk.bigint? +      assert legacy_pk.auto_increment? + +      schema = dump_table_schema "legacy_primary_keys" +      assert_match %r{create_table "legacy_primary_keys", (?!id: :bigint, default: nil)}, schema +    end +  else +    def test_legacy_bigint_primary_key_should_not_be_auto_incremented +      @migration = Class.new(ActiveRecord::Migration[5.0]) { +        def change +          create_table :legacy_primary_keys, id: :bigint do |t| +          end +        end +      }.new + +      @migration.migrate(:up) + +      assert_raises(ActiveRecord::NotNullViolation) do +        LegacyPrimaryKey.create!        end + +      schema = dump_table_schema "legacy_primary_keys" +      assert_match %r{create_table "legacy_primary_keys", id: :bigint, default: nil}, schema      end    end  end diff --git a/activerecord/test/cases/migration/create_join_table_test.rb b/activerecord/test/cases/migration/create_join_table_test.rb index f14d68f12b..26b1bb4419 100644 --- a/activerecord/test/cases/migration/create_join_table_test.rb +++ b/activerecord/test/cases/migration/create_join_table_test.rb @@ -12,9 +12,7 @@ module ActiveRecord        teardown do          %w(artists_musics musics_videos catalog).each do |table_name| -          ActiveSupport::Deprecation.silence do -            connection.drop_table table_name if connection.table_exists?(table_name) -          end +          connection.drop_table table_name if connection.table_exists?(table_name)          end        end @@ -84,51 +82,51 @@ module ActiveRecord          connection.create_join_table :artists, :musics          connection.drop_join_table :artists, :musics -        ActiveSupport::Deprecation.silence { assert !connection.table_exists?("artists_musics") } +        assert !connection.table_exists?("artists_musics")        end        def test_drop_join_table_with_strings          connection.create_join_table :artists, :musics          connection.drop_join_table "artists", "musics" -        ActiveSupport::Deprecation.silence { assert !connection.table_exists?("artists_musics") } +        assert !connection.table_exists?("artists_musics")        end        def test_drop_join_table_with_the_proper_order          connection.create_join_table :videos, :musics          connection.drop_join_table :videos, :musics -        ActiveSupport::Deprecation.silence { assert !connection.table_exists?("musics_videos") } +        assert !connection.table_exists?("musics_videos")        end        def test_drop_join_table_with_the_table_name          connection.create_join_table :artists, :musics, table_name: :catalog          connection.drop_join_table :artists, :musics, table_name: :catalog -        ActiveSupport::Deprecation.silence { assert !connection.table_exists?("catalog") } +        assert !connection.table_exists?("catalog")        end        def test_drop_join_table_with_the_table_name_as_string          connection.create_join_table :artists, :musics, table_name: "catalog"          connection.drop_join_table :artists, :musics, table_name: "catalog" -        ActiveSupport::Deprecation.silence { assert !connection.table_exists?("catalog") } +        assert !connection.table_exists?("catalog")        end        def test_drop_join_table_with_column_options          connection.create_join_table :artists, :musics, column_options: { null: true }          connection.drop_join_table :artists, :musics, column_options: { null: true } -        ActiveSupport::Deprecation.silence { assert !connection.table_exists?("artists_musics") } +        assert !connection.table_exists?("artists_musics")        end        def test_create_and_drop_join_table_with_common_prefix          with_table_cleanup do            connection.create_join_table "audio_artists", "audio_musics" -          ActiveSupport::Deprecation.silence { assert connection.table_exists?("audio_artists_musics") } +          assert connection.table_exists?("audio_artists_musics")            connection.drop_join_table "audio_artists", "audio_musics" -          ActiveSupport::Deprecation.silence { assert !connection.table_exists?("audio_artists_musics"), "Should have dropped join table, but didn't" } +          assert !connection.table_exists?("audio_artists_musics"), "Should have dropped join table, but didn't"          end        end diff --git a/activerecord/test/cases/migration/foreign_key_test.rb b/activerecord/test/cases/migration/foreign_key_test.rb index 9be6667aa1..96e775a58b 100644 --- a/activerecord/test/cases/migration/foreign_key_test.rb +++ b/activerecord/test/cases/migration/foreign_key_test.rb @@ -2,6 +2,26 @@ require "cases/helper"  require "support/ddl_helper"  require "support/schema_dumping_helper" +if ActiveRecord::Base.connection.supports_foreign_keys_in_create? +  module ActiveRecord +    class Migration +      class ForeignKeyInCreateTest < ActiveRecord::TestCase +        def test_foreign_keys +          foreign_keys = ActiveRecord::Base.connection.foreign_keys("fk_test_has_fk") +          assert_equal 1, foreign_keys.size + +          fk = foreign_keys.first +          assert_equal "fk_test_has_fk", fk.from_table +          assert_equal "fk_test_has_pk", fk.to_table +          assert_equal "fk_id", fk.column +          assert_equal "pk_id", fk.primary_key +          assert_equal "fk_name", fk.name unless current_adapter?(:SQLite3Adapter) +        end +      end +    end +  end +end +  if ActiveRecord::Base.connection.supports_foreign_keys?    module ActiveRecord      class Migration @@ -29,10 +49,8 @@ if ActiveRecord::Base.connection.supports_foreign_keys?          end          teardown do -          if defined?(@connection) -            @connection.drop_table "astronauts", if_exists: true -            @connection.drop_table "rockets", if_exists: true -          end +          @connection.drop_table "astronauts", if_exists: true +          @connection.drop_table "rockets", if_exists: true          end          def test_foreign_keys @@ -305,9 +323,11 @@ else            @connection.remove_foreign_key :clubs, :categories          end -        def test_foreign_keys_should_raise_not_implemented -          assert_raises NotImplementedError do -            @connection.foreign_keys("clubs") +        unless current_adapter?(:SQLite3Adapter) +          def test_foreign_keys_should_raise_not_implemented +            assert_raises NotImplementedError do +              @connection.foreign_keys("clubs") +            end            end          end        end diff --git a/activerecord/test/cases/migration/references_foreign_key_test.rb b/activerecord/test/cases/migration/references_foreign_key_test.rb index 4957ab8b3d..560adcbfed 100644 --- a/activerecord/test/cases/migration/references_foreign_key_test.rb +++ b/activerecord/test/cases/migration/references_foreign_key_test.rb @@ -1,9 +1,9 @@  require "cases/helper" -if ActiveRecord::Base.connection.supports_foreign_keys? +if ActiveRecord::Base.connection.supports_foreign_keys_in_create?    module ActiveRecord      class Migration -      class ReferencesForeignKeyTest < ActiveRecord::TestCase +      class ReferencesForeignKeyInCreateTest < ActiveRecord::TestCase          setup do            @connection = ActiveRecord::Base.connection            @connection.create_table(:testing_parents, force: true) @@ -61,6 +61,24 @@ if ActiveRecord::Base.connection.supports_foreign_keys?            assert_equal([["testings", "testing_parents", "parent_id"]],                         fks.map { |fk| [fk.from_table, fk.to_table, fk.column] })          end +      end +    end +  end +end + +if ActiveRecord::Base.connection.supports_foreign_keys? +  module ActiveRecord +    class Migration +      class ReferencesForeignKeyTest < ActiveRecord::TestCase +        setup do +          @connection = ActiveRecord::Base.connection +          @connection.create_table(:testing_parents, force: true) +        end + +        teardown do +          @connection.drop_table "testings", if_exists: true +          @connection.drop_table "testing_parents", if_exists: true +        end          test "foreign keys cannot be added to polymorphic relations when creating the table" do            @connection.create_table :testings do |t| diff --git a/activerecord/test/cases/migration/rename_table_test.rb b/activerecord/test/cases/migration/rename_table_test.rb index fc4f700916..19588d28a2 100644 --- a/activerecord/test/cases/migration/rename_table_test.rb +++ b/activerecord/test/cases/migration/rename_table_test.rb @@ -15,7 +15,7 @@ module ActiveRecord        end        def teardown -        ActiveSupport::Deprecation.silence { rename_table :octopi, :test_models if connection.table_exists? :octopi } +        rename_table :octopi, :test_models if connection.table_exists? :octopi          super        end @@ -82,7 +82,7 @@ module ActiveRecord          def test_renaming_table_doesnt_attempt_to_rename_non_existent_sequences            connection.create_table :cats, id: :uuid            assert_nothing_raised { rename_table :cats, :felines } -          ActiveSupport::Deprecation.silence { assert connection.table_exists? :felines } +          assert connection.table_exists? :felines          ensure            connection.drop_table :cats, if_exists: true            connection.drop_table :felines, if_exists: true diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index 082cfd3242..de16ecf442 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -43,10 +43,10 @@ class MigrationTest < ActiveRecord::TestCase      ActiveRecord::Base.table_name_prefix = ""      ActiveRecord::Base.table_name_suffix = "" -    ActiveRecord::Base.connection.initialize_schema_migrations_table -    ActiveRecord::Base.connection.execute "DELETE FROM #{ActiveRecord::Migrator.schema_migrations_table_name}" +    ActiveRecord::SchemaMigration.create_table +    ActiveRecord::SchemaMigration.delete_all -    %w(things awesome_things prefix_things_suffix p_awesome_things_s ).each do |table| +    %w(things awesome_things prefix_things_suffix p_awesome_things_s).each do |table|        Thing.connection.drop_table(table) rescue nil      end      Thing.reset_column_information @@ -399,6 +399,7 @@ class MigrationTest < ActiveRecord::TestCase      ActiveRecord::Migrator.migrations_paths = old_path      ENV["RAILS_ENV"] = original_rails_env      ENV["RACK_ENV"]  = original_rack_env +    ActiveRecord::Migrator.up(migrations_path)    end    def test_migration_sets_internal_metadata_even_when_fully_migrated @@ -425,6 +426,7 @@ class MigrationTest < ActiveRecord::TestCase      ActiveRecord::Migrator.migrations_paths = old_path      ENV["RAILS_ENV"] = original_rails_env      ENV["RACK_ENV"]  = original_rack_env +    ActiveRecord::Migrator.up(migrations_path)    end    def test_internal_metadata_stores_environment_when_other_data_exists @@ -887,7 +889,7 @@ if ActiveRecord::Base.connection.supports_bulk_alter?        assert_equal :datetime, column(:birthdate).type      end -    protected +    private        def with_bulk_change_table          # Reset columns/indexes cache as we're changing the table @@ -914,7 +916,6 @@ if ActiveRecord::Base.connection.supports_bulk_alter?          @indexes ||= Person.connection.indexes("delete_me")        end    end # AlterTableMigrationsTest -  end  class CopyMigrationsTest < ActiveRecord::TestCase @@ -1132,4 +1133,13 @@ class CopyMigrationsTest < ActiveRecord::TestCase    def test_unknown_migration_version_should_raise_an_argument_error      assert_raise(ArgumentError) { ActiveRecord::Migration[1.0] }    end + +  def test_deprecate_initialize_internal_tables +    assert_deprecated { ActiveRecord::Base.connection.initialize_schema_migrations_table } +    assert_deprecated { ActiveRecord::Base.connection.initialize_internal_metadata_table } +  end + +  def test_deprecate_migration_keys +    assert_deprecated { ActiveRecord::Base.connection.migration_keys } +  end  end diff --git a/activerecord/test/cases/migrator_test.rb b/activerecord/test/cases/migrator_test.rb index b775bf0492..20d70b75ac 100644 --- a/activerecord/test/cases/migrator_test.rb +++ b/activerecord/test/cases/migrator_test.rb @@ -45,10 +45,11 @@ class MigratorTest < ActiveRecord::TestCase    end    def test_migrator_with_duplicate_names -    assert_raises(ActiveRecord::DuplicateMigrationNameError, "Multiple migrations have the name Chunky") do +    e = assert_raises(ActiveRecord::DuplicateMigrationNameError) do        list = [ActiveRecord::Migration.new("Chunky"), ActiveRecord::Migration.new("Chunky")]        ActiveRecord::Migrator.new(:up, list)      end +    assert_match(/Multiple migrations have the name Chunky/, e.message)    end    def test_migrator_with_duplicate_versions @@ -290,6 +291,27 @@ class MigratorTest < ActiveRecord::TestCase      assert_equal [[:up, 1], [:up, 2], [:up, 3]], calls    end +  def test_migrator_output_when_running_multiple_migrations +    _, migrator = migrator_class(3) + +    result = migrator.migrate("valid") +    assert_equal(3, result.count) + +    # Nothing migrated from duplicate run +    result = migrator.migrate("valid") +    assert_equal(0, result.count) + +    result = migrator.rollback("valid") +    assert_equal(1, result.count) +  end + +  def test_migrator_output_when_running_single_migration +    _, migrator = migrator_class(1) +    result = migrator.run(:up, "valid", 1) + +    assert_equal(1, result.version) +  end +    def test_migrator_rollback      _, migrator = migrator_class(3) @@ -313,9 +335,9 @@ class MigratorTest < ActiveRecord::TestCase      _, migrator = migrator_class(3)      ActiveRecord::Base.connection.drop_table "schema_migrations", if_exists: true -    ActiveSupport::Deprecation.silence { assert_not ActiveRecord::Base.connection.table_exists?("schema_migrations") } +    assert_not ActiveRecord::Base.connection.table_exists?("schema_migrations")      migrator.migrate("valid", 1) -    ActiveSupport::Deprecation.silence { assert ActiveRecord::Base.connection.table_exists?("schema_migrations") } +    assert ActiveRecord::Base.connection.table_exists?("schema_migrations")    end    def test_migrator_forward diff --git a/activerecord/test/cases/nested_attributes_with_callbacks_test.rb b/activerecord/test/cases/nested_attributes_with_callbacks_test.rb index 350a966d40..b9d2acbed2 100644 --- a/activerecord/test/cases/nested_attributes_with_callbacks_test.rb +++ b/activerecord/test/cases/nested_attributes_with_callbacks_test.rb @@ -120,14 +120,14 @@ class NestedAttributesWithCallbacksTest < ActiveRecord::TestCase      assert_assignment_affects_records_in_target(:birds_with_add)    end -  test("Assignment updates records in target when not loaded" + +  test("Assignment updates records in target when not loaded" \         " and callback loads target") do      assert_not @pirate.birds_with_add_load.loaded?      @pirate.birds_with_add_load_attributes = update_new_and_destroy_bird_attributes      assert_assignment_affects_records_in_target(:birds_with_add_load)    end -  test("Assignment updates records in target when loaded" + +  test("Assignment updates records in target when loaded" \         " and callback loads target") do      @pirate.birds_with_add_load.load_target      @pirate.birds_with_add_load_attributes = update_new_and_destroy_bird_attributes diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index 3f1da82cb4..5b7e2fd008 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -139,6 +139,14 @@ class PersistenceTest < ActiveRecord::TestCase      assert_equal initial_credit + 2, a1.reload.credit_limit    end +  def test_increment_updates_timestamps +    topic = topics(:first) +    topic.update_columns(updated_at: 5.minutes.ago) +    previous_updated_at = topic.updated_at +    topic.increment!(:replies_count, touch: true) +    assert_operator previous_updated_at, :<, topic.reload.updated_at +  end +    def test_destroy_all      conditions = "author_name = 'Mary'"      topics_by_mary = Topic.all.merge!(where: conditions, order: "id").to_a @@ -230,6 +238,14 @@ class PersistenceTest < ActiveRecord::TestCase      assert_equal 41, accounts(:signals37, :reload).credit_limit    end +  def test_decrement_updates_timestamps +    topic = topics(:first) +    topic.update_columns(updated_at: 5.minutes.ago) +    previous_updated_at = topic.updated_at +    topic.decrement!(:replies_count, touch: true) +    assert_operator previous_updated_at, :<, topic.reload.updated_at +  end +    def test_create      topic = Topic.new      topic.title = "New Topic" diff --git a/activerecord/test/cases/primary_keys_test.rb b/activerecord/test/cases/primary_keys_test.rb index 1d72899102..f92ee15cfa 100644 --- a/activerecord/test/cases/primary_keys_test.rb +++ b/activerecord/test/cases/primary_keys_test.rb @@ -319,70 +319,87 @@ class CompositePrimaryKeyTest < ActiveRecord::TestCase    end  end -if current_adapter?(:Mysql2Adapter) -  class PrimaryKeyIntegerNilDefaultTest < ActiveRecord::TestCase -    include SchemaDumpingHelper +class PrimaryKeyIntegerNilDefaultTest < ActiveRecord::TestCase +  include SchemaDumpingHelper -    self.use_transactional_tests = false +  self.use_transactional_tests = false -    def setup -      @connection = ActiveRecord::Base.connection -      @connection.create_table(:int_defaults, id: :integer, default: nil, force: true) -    end +  def setup +    @connection = ActiveRecord::Base.connection +  end -    def teardown -      @connection.drop_table :int_defaults, if_exists: true -    end +  def teardown +    @connection.drop_table :int_defaults, if_exists: true +  end -    test "primary key with integer allows default override via nil" do -      column = @connection.columns(:int_defaults).find { |c| c.name == "id" } -      assert_equal :integer, column.type -      assert_not column.auto_increment? -    end +  def test_schema_dump_primary_key_integer_with_default_nil +    skip if current_adapter?(:SQLite3Adapter) +    @connection.create_table(:int_defaults, id: :integer, default: nil, force: true) +    schema = dump_table_schema "int_defaults" +    assert_match %r{create_table "int_defaults", id: :integer, default: nil}, schema +  end -    test "schema dump primary key with int default nil" do -      schema = dump_table_schema "int_defaults" -      assert_match %r{create_table "int_defaults", id: :integer, default: nil}, schema -    end +  def test_schema_dump_primary_key_bigint_with_default_nil +    @connection.create_table(:int_defaults, id: :bigint, default: nil, force: true) +    schema = dump_table_schema "int_defaults" +    assert_match %r{create_table "int_defaults", id: :bigint, default: nil}, schema    end  end -class PrimaryKeyIntegerTest < ActiveRecord::TestCase -  include SchemaDumpingHelper +if current_adapter?(:PostgreSQLAdapter, :Mysql2Adapter) +  class PrimaryKeyIntegerTest < ActiveRecord::TestCase +    include SchemaDumpingHelper -  self.use_transactional_tests = false +    self.use_transactional_tests = false -  class Widget < ActiveRecord::Base -  end +    class Widget < ActiveRecord::Base +    end -  setup do -    @connection = ActiveRecord::Base.connection -    @connection.create_table(:widgets, force: true) -  end +    setup do +      @connection = ActiveRecord::Base.connection +      if current_adapter?(:PostgreSQLAdapter) +        @connection.create_table(:widgets, id: :serial, force: true) +      else +        @connection.create_table(:widgets, id: :integer, force: true) +      end +    end -  teardown do -    @connection.drop_table :widgets, if_exists: true -    Widget.reset_column_information -  end +    teardown do +      @connection.drop_table :widgets, if_exists: true +    end -  if current_adapter?(:PostgreSQLAdapter, :Mysql2Adapter) -    test "schema dump primary key with bigserial" do -      schema = dump_table_schema "widgets" -      assert_match %r{create_table "widgets", force: :cascade}, schema +    test "primary key column type with serial/integer" do +      column = @connection.columns(:widgets).find { |c| c.name == "id" } +      assert_equal :integer, column.type +      assert_not column.bigint?      end -  end -  test "primary key column type" do -    column_type = Widget.type_for_attribute(Widget.primary_key) -    assert_equal :integer, column_type.type +    test "primary key with serial/integer are automatically numbered" do +      widget = Widget.create! +      assert_not_nil widget.id +    end -    if current_adapter?(:PostgreSQLAdapter, :Mysql2Adapter) -      assert_equal 8, column_type.limit +    test "schema dump primary key with serial/integer" do +      schema = dump_table_schema "widgets" +      if current_adapter?(:PostgreSQLAdapter) +        assert_match %r{create_table "widgets", id: :serial, force: :cascade}, schema +      else +        assert_match %r{create_table "widgets", id: :integer, force: :cascade}, schema +      end      end      if current_adapter?(:Mysql2Adapter) -      column = @connection.columns(:widgets).find { |c| c.name == "id" } -      assert column.auto_increment? +      test "primary key column type with options" do +        @connection.create_table(:widgets, id: :primary_key, limit: 4, unsigned: true, force: true) +        column = @connection.columns(:widgets).find { |c| c.name == "id" } +        assert column.auto_increment? +        assert_equal :integer, column.type +        assert_equal 4, column.limit +        assert column.unsigned? + +        schema = dump_table_schema "widgets" +        assert_match %r{create_table "widgets", id: :integer, unsigned: true, force: :cascade}, schema +      end      end    end  end diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb index 4a49bfe9b1..d8cf235000 100644 --- a/activerecord/test/cases/query_cache_test.rb +++ b/activerecord/test/cases/query_cache_test.rb @@ -50,32 +50,36 @@ class QueryCacheTest < ActiveRecord::TestCase      assert_cache :off    end -  def test_query_cache_across_threads -    ActiveRecord::Base.connection_pool.connections.each do |conn| -      assert_cache :off, conn -    end - -    assert !ActiveRecord::Base.connection.nil? -    assert_cache :off - -    middleware { -      assert_cache :clean - -      Task.find 1 -      assert_cache :dirty +  private def with_temporary_connection_pool +    old_pool = ActiveRecord::Base.connection_handler.retrieve_connection_pool(ActiveRecord::Base.connection_specification_name) +    new_pool = ActiveRecord::ConnectionAdapters::ConnectionPool.new ActiveRecord::Base.connection_pool.spec +    ActiveRecord::Base.connection_handler.send(:owner_to_pool)["primary"] = new_pool -      thread_1_connection = ActiveRecord::Base.connection -      ActiveRecord::Base.clear_active_connections! -      assert_cache :off, thread_1_connection +    yield +  ensure +    ActiveRecord::Base.connection_handler.send(:owner_to_pool)["primary"] = old_pool +  end -      started = Concurrent::Event.new -      checked = Concurrent::Event.new +  def test_query_cache_across_threads +    with_temporary_connection_pool do +      begin +        if in_memory_db? +          # Separate connections to an in-memory database create an entirely new database, +          # with an empty schema etc, so we just stub out this schema on the fly. +          ActiveRecord::Base.connection_pool.with_connection do |connection| +            connection.create_table :tasks do |t| +              t.datetime :starting +              t.datetime :ending +            end +          end +          ActiveRecord::FixtureSet.create_fixtures(self.class.fixture_path, ["tasks"], {}, ActiveRecord::Base) +        end -      thread_2_connection = nil -      thread = Thread.new { -        thread_2_connection = ActiveRecord::Base.connection +        ActiveRecord::Base.connection_pool.connections.each do |conn| +          assert_cache :off, conn +        end -        assert_equal thread_2_connection, thread_1_connection +        assert !ActiveRecord::Base.connection.nil?          assert_cache :off          middleware { @@ -84,29 +88,51 @@ class QueryCacheTest < ActiveRecord::TestCase            Task.find 1            assert_cache :dirty -          started.set -          checked.wait - +          thread_1_connection = ActiveRecord::Base.connection            ActiveRecord::Base.clear_active_connections! -        }.call({}) -      } +          assert_cache :off, thread_1_connection + +          started = Concurrent::Event.new +          checked = Concurrent::Event.new + +          thread_2_connection = nil +          thread = Thread.new { +            thread_2_connection = ActiveRecord::Base.connection + +            assert_equal thread_2_connection, thread_1_connection +            assert_cache :off + +            middleware { +              assert_cache :clean + +              Task.find 1 +              assert_cache :dirty -      started.wait +              started.set +              checked.wait -      thread_1_connection = ActiveRecord::Base.connection -      assert_not_equal thread_1_connection, thread_2_connection -      assert_cache :dirty, thread_2_connection -      checked.set -      thread.join +              ActiveRecord::Base.clear_active_connections! +            }.call({}) +          } -      assert_cache :off, thread_2_connection -    }.call({}) +          started.wait -    ActiveRecord::Base.connection_pool.connections.each do |conn| -      assert_cache :off, conn +          thread_1_connection = ActiveRecord::Base.connection +          assert_not_equal thread_1_connection, thread_2_connection +          assert_cache :dirty, thread_2_connection +          checked.set +          thread.join + +          assert_cache :off, thread_2_connection +        }.call({}) + +        ActiveRecord::Base.connection_pool.connections.each do |conn| +          assert_cache :off, conn +        end +      ensure +        ActiveRecord::Base.clear_all_connections! +      end      end -  ensure -    ActiveRecord::Base.clear_all_connections!    end    def test_middleware_delegates @@ -260,19 +286,51 @@ class QueryCacheTest < ActiveRecord::TestCase    end    def test_cache_is_not_available_when_using_a_not_connected_connection -    spec_name = Task.connection_specification_name -    conf = ActiveRecord::Base.configurations["arunit"].merge("name" => "test2") -    ActiveRecord::Base.connection_handler.establish_connection(conf) -    Task.connection_specification_name = "test2" -    refute Task.connected? +    with_temporary_connection_pool do +      spec_name = Task.connection_specification_name +      conf = ActiveRecord::Base.configurations["arunit"].merge("name" => "test2") +      ActiveRecord::Base.connection_handler.establish_connection(conf) +      Task.connection_specification_name = "test2" +      refute Task.connected? -    Task.cache do -      Task.connection # warmup postgresql connection setup queries -      assert_queries(2) { Task.find(1); Task.find(1) } +      Task.cache do +        begin +          if in_memory_db? +            Task.connection.create_table :tasks do |t| +              t.datetime :starting +              t.datetime :ending +            end +            ActiveRecord::FixtureSet.create_fixtures(self.class.fixture_path, ["tasks"], {}, ActiveRecord::Base) +          end +          Task.connection # warmup postgresql connection setup queries +          assert_queries(2) { Task.find(1); Task.find(1) } +        ensure +          ActiveRecord::Base.connection_handler.remove_connection(Task.connection_specification_name) +          Task.connection_specification_name = spec_name +        end +      end +    end +  end + +  def test_query_cache_executes_new_queries_within_block +    ActiveRecord::Base.connection.enable_query_cache! + +    # Warm up the cache by running the query +    assert_queries(1) do +      assert_equal 0, Post.where(title: "test").to_a.count +    end + +    # Check that if the same query is run again, no queries are executed +    assert_queries(0) do +      assert_equal 0, Post.where(title: "test").to_a.count +    end + +    ActiveRecord::Base.connection.uncached do +      # Check that new query is executed, avoiding the cache +      assert_queries(1) do +        assert_equal 0, Post.where(title: "test").to_a.count +      end      end -  ensure -    ActiveRecord::Base.connection_handler.remove_connection(Task.connection_specification_name) -    Task.connection_specification_name = spec_name    end    def test_query_cache_doesnt_leak_cached_results_of_rolled_back_queries @@ -328,37 +386,44 @@ class QueryCacheTest < ActiveRecord::TestCase    end    def test_query_cache_does_not_establish_connection_if_unconnected -    ActiveRecord::Base.clear_active_connections! -    refute ActiveRecord::Base.connection_handler.active_connections? # sanity check +    with_temporary_connection_pool do +      ActiveRecord::Base.clear_active_connections! +      refute ActiveRecord::Base.connection_handler.active_connections? # sanity check -    middleware { -      refute ActiveRecord::Base.connection_handler.active_connections?, "QueryCache forced ActiveRecord::Base to establish a connection in setup" -    }.call({}) +      middleware { +        refute ActiveRecord::Base.connection_handler.active_connections?, "QueryCache forced ActiveRecord::Base to establish a connection in setup" +      }.call({}) -    refute ActiveRecord::Base.connection_handler.active_connections?, "QueryCache forced ActiveRecord::Base to establish a connection in cleanup" +      refute ActiveRecord::Base.connection_handler.active_connections?, "QueryCache forced ActiveRecord::Base to establish a connection in cleanup" +    end    end    def test_query_cache_is_enabled_on_connections_established_after_middleware_runs -    ActiveRecord::Base.clear_active_connections! -    refute ActiveRecord::Base.connection_handler.active_connections? # sanity check +    with_temporary_connection_pool do +      ActiveRecord::Base.clear_active_connections! +      refute ActiveRecord::Base.connection_handler.active_connections? # sanity check -    middleware { -      assert ActiveRecord::Base.connection.query_cache_enabled, "QueryCache did not get lazily enabled" -    }.call({}) +      middleware { +        assert ActiveRecord::Base.connection.query_cache_enabled, "QueryCache did not get lazily enabled" +      }.call({}) +    end    end    def test_query_caching_is_local_to_the_current_thread -    ActiveRecord::Base.clear_active_connections! +    with_temporary_connection_pool do +      ActiveRecord::Base.clear_active_connections! -    middleware { -      assert ActiveRecord::Base.connection_pool.query_cache_enabled -      assert ActiveRecord::Base.connection.query_cache_enabled +      middleware { +        assert ActiveRecord::Base.connection_pool.query_cache_enabled +        assert ActiveRecord::Base.connection.query_cache_enabled -      Thread.new { -        refute ActiveRecord::Base.connection_pool.query_cache_enabled -        refute ActiveRecord::Base.connection.query_cache_enabled -      }.join -    }.call({}) +        Thread.new { +          refute ActiveRecord::Base.connection_pool.query_cache_enabled +          refute ActiveRecord::Base.connection.query_cache_enabled +        }.join +      }.call({}) + +    end    end    private @@ -388,6 +453,10 @@ end  class QueryCacheExpiryTest < ActiveRecord::TestCase    fixtures :tasks, :posts, :categories, :categories_posts +  def teardown +    Task.connection.clear_query_cache +  end +    def test_cache_gets_cleared_after_migration      # warm the cache      Post.find(1) diff --git a/activerecord/test/cases/quoting_test.rb b/activerecord/test/cases/quoting_test.rb index 05b71638c1..5ff5e3c735 100644 --- a/activerecord/test/cases/quoting_test.rb +++ b/activerecord/test/cases/quoting_test.rb @@ -82,46 +82,46 @@ module ActiveRecord        end        def test_quote_with_quoted_id -        assert_equal 1, @quoter.quote(Struct.new(:quoted_id).new(1), nil) +        assert_equal 1, @quoter.quote(Struct.new(:quoted_id).new(1))        end        def test_quote_nil -        assert_equal "NULL", @quoter.quote(nil, nil) +        assert_equal "NULL", @quoter.quote(nil)        end        def test_quote_true -        assert_equal @quoter.quoted_true, @quoter.quote(true, nil) +        assert_equal @quoter.quoted_true, @quoter.quote(true)        end        def test_quote_false -        assert_equal @quoter.quoted_false, @quoter.quote(false, nil) +        assert_equal @quoter.quoted_false, @quoter.quote(false)        end        def test_quote_float          float = 1.2 -        assert_equal float.to_s, @quoter.quote(float, nil) +        assert_equal float.to_s, @quoter.quote(float)        end        def test_quote_integer          integer = 1 -        assert_equal integer.to_s, @quoter.quote(integer, nil) +        assert_equal integer.to_s, @quoter.quote(integer)        end        def test_quote_bignum          bignum = 1 << 100 -        assert_equal bignum.to_s, @quoter.quote(bignum, nil) +        assert_equal bignum.to_s, @quoter.quote(bignum)        end        def test_quote_bigdecimal          bigdec = BigDecimal.new((1 << 100).to_s) -        assert_equal bigdec.to_s("F"), @quoter.quote(bigdec, nil) +        assert_equal bigdec.to_s("F"), @quoter.quote(bigdec)        end        def test_dates_and_times          @quoter.extend(Module.new { def quoted_date(value) "lol" end }) -        assert_equal "'lol'", @quoter.quote(Date.today, nil) -        assert_equal "'lol'", @quoter.quote(Time.now, nil) -        assert_equal "'lol'", @quoter.quote(DateTime.now, nil) +        assert_equal "'lol'", @quoter.quote(Date.today) +        assert_equal "'lol'", @quoter.quote(Time.now) +        assert_equal "'lol'", @quoter.quote(DateTime.now)        end        def test_quoting_classes @@ -131,7 +131,7 @@ module ActiveRecord        def test_crazy_object          crazy = Object.new          e = assert_raises(TypeError) do -          @quoter.quote(crazy, nil) +          @quoter.quote(crazy)          end          assert_equal "can't quote Object", e.message        end diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb index a90058e8bb..c1c2efb9c8 100644 --- a/activerecord/test/cases/reflection_test.rb +++ b/activerecord/test/cases/reflection_test.rb @@ -100,7 +100,13 @@ class ReflectionTest < ActiveRecord::TestCase    end    def test_reflection_klass_for_nested_class_name -    reflection = ActiveRecord::Reflection.create(:has_many, nil, nil, { class_name: "MyApplication::Business::Company" }, ActiveRecord::Base) +    reflection = ActiveRecord::Reflection.create( +      :has_many, +      nil, +      nil, +      { class_name: "MyApplication::Business::Company" }, +      Customer +    )      assert_nothing_raised do        assert_equal MyApplication::Business::Company, reflection.klass      end @@ -252,7 +258,9 @@ class ReflectionTest < ActiveRecord::TestCase        [Post.reflect_on_association(:first_taggings).scope],        [Author.reflect_on_association(:misc_posts).scope]      ] -    actual = Author.reflect_on_association(:misc_post_first_blue_tags).scope_chain +    actual = assert_deprecated do +      Author.reflect_on_association(:misc_post_first_blue_tags).scope_chain +    end      assert_equal expected, actual      expected = [ @@ -264,7 +272,9 @@ class ReflectionTest < ActiveRecord::TestCase        [],        []      ] -    actual = Author.reflect_on_association(:misc_post_first_blue_tags_2).scope_chain +    actual = assert_deprecated do +      Author.reflect_on_association(:misc_post_first_blue_tags_2).scope_chain +    end      assert_equal expected, actual    end @@ -325,6 +335,15 @@ class ReflectionTest < ActiveRecord::TestCase      assert_equal "custom_primary_key", Author.reflect_on_association(:tags_with_primary_key).association_primary_key.to_s # nested    end +  def test_association_primary_key_type +    # Normal Association +    assert_equal :integer, Author.reflect_on_association(:posts).association_primary_key_type.type +    assert_equal :string,  Author.reflect_on_association(:essay).association_primary_key_type.type + +    # Through Association +    assert_equal :string, Author.reflect_on_association(:essay_category).association_primary_key_type.type +  end +    def test_association_primary_key_raises_when_missing_primary_key      reflection = ActiveRecord::Reflection.create(:has_many, :edge, nil, {}, Author)      assert_raises(ActiveRecord::UnknownPrimaryKey) { reflection.association_primary_key } @@ -389,15 +408,27 @@ class ReflectionTest < ActiveRecord::TestCase    end    def test_through_reflection_scope_chain_does_not_modify_other_reflections -    orig_conds = Post.reflect_on_association(:first_blue_tags_2).scope_chain.inspect -    Author.reflect_on_association(:misc_post_first_blue_tags_2).scope_chain -    assert_equal orig_conds, Post.reflect_on_association(:first_blue_tags_2).scope_chain.inspect +    orig_conds = assert_deprecated do +      Post.reflect_on_association(:first_blue_tags_2).scope_chain +    end.inspect +    assert_deprecated do +      Author.reflect_on_association(:misc_post_first_blue_tags_2).scope_chain +    end +    assert_equal orig_conds, assert_deprecated { +      Post.reflect_on_association(:first_blue_tags_2).scope_chain +    }.inspect    end    def test_symbol_for_class_name      assert_equal Client, Firm.reflect_on_association(:unsorted_clients_with_symbol).klass    end +  def test_class_for_class_name +    assert_deprecated do +      assert_predicate ActiveRecord::Reflection.create(:has_many, :clients, nil, { class_name: Client }, Firm), :validate? +    end +  end +    def test_join_table      category = Struct.new(:table_name, :pluralize_table_names).new("categories", true)      product = Struct.new(:table_name, :pluralize_table_names).new("products", true) diff --git a/activerecord/test/cases/relation/mutation_test.rb b/activerecord/test/cases/relation/mutation_test.rb index 966ae83a3f..4f92f71a09 100644 --- a/activerecord/test/cases/relation/mutation_test.rb +++ b/activerecord/test/cases/relation/mutation_test.rb @@ -3,7 +3,7 @@ require "models/post"  module ActiveRecord    class RelationMutationTest < ActiveRecord::TestCase -    class FakeKlass < Struct.new(:table_name, :name) +    FakeKlass = Struct.new(:table_name, :name) do        extend ActiveRecord::Delegation::DelegateCache        inherited self @@ -90,7 +90,7 @@ module ActiveRecord        assert_equal [], relation.extending_values      end -    (Relation::SINGLE_VALUE_METHODS - [:lock, :reordering, :reverse_order, :create_with, :uniq]).each do |method| +    (Relation::SINGLE_VALUE_METHODS - [:lock, :reordering, :reverse_order, :create_with]).each do |method|        test "##{method}!" do          assert relation.public_send("#{method}!", :foo).equal?(relation)          assert_equal :foo, relation.public_send("#{method}_value") @@ -108,7 +108,7 @@ module ActiveRecord      end      test "#reorder!" do -      @relation = self.relation.order("foo") +      @relation = relation.order("foo")        assert relation.reorder!("bar").equal?(relation)        assert_equal ["bar"], relation.order_values @@ -161,22 +161,6 @@ module ActiveRecord      test "distinct!" do        relation.distinct! :foo        assert_equal :foo, relation.distinct_value - -      assert_deprecated do -        assert_equal :foo, relation.uniq_value # deprecated access -      end -    end - -    test "uniq! was replaced by distinct!" do -      assert_deprecated(/use distinct! instead/) do -        relation.uniq! :foo -      end - -      assert_deprecated(/use distinct_value instead/) do -        assert_equal :foo, relation.uniq_value # deprecated access -      end - -      assert_equal :foo, relation.distinct_value      end    end  end diff --git a/activerecord/test/cases/relation/or_test.rb b/activerecord/test/cases/relation/or_test.rb index 2796595523..abb7ca72dd 100644 --- a/activerecord/test/cases/relation/or_test.rb +++ b/activerecord/test/cases/relation/or_test.rb @@ -79,7 +79,7 @@ module ActiveRecord        expected = Post.where("id = 1 or id = 2").to_a        p = Post.where("id = 1")        p.load -      assert_equal p.loaded?, true +      assert_equal true, p.loaded?        assert_equal expected, p.or(Post.where("id = 2")).to_a      end diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb index d5af0cc9a5..1241bb54a4 100644 --- a/activerecord/test/cases/relation_test.rb +++ b/activerecord/test/cases/relation_test.rb @@ -8,7 +8,7 @@ module ActiveRecord    class RelationTest < ActiveRecord::TestCase      fixtures :posts, :comments, :authors -    class FakeKlass < Struct.new(:table_name, :name) +    FakeKlass = Struct.new(:table_name, :name) do        extend ActiveRecord::Delegation::DelegateCache        inherited self diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 981c2d758d..dc6311e8bc 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -840,15 +840,6 @@ class RelationTest < ActiveRecord::TestCase      assert_equal author, authors.first    end -  class Mary < Author; end - -  def test_find_by_classname -    Author.create!(name: Mary.name) -    assert_deprecated do -      assert_equal 1, Author.where(name: Mary).size -    end -  end -    def test_find_by_id_with_list_of_ar      author = Author.first      authors = Author.find_by_id([author]) @@ -1013,12 +1004,6 @@ class RelationTest < ActiveRecord::TestCase      assert davids.loaded?    end -  def test_destroy_all_with_conditions_is_deprecated -    assert_deprecated do -      assert_difference("Author.count", -1) { Author.destroy_all(name: "David") } -    end -  end -    def test_delete_all      davids = Author.where(name: "David") @@ -1026,12 +1011,6 @@ class RelationTest < ActiveRecord::TestCase      assert ! davids.loaded?    end -  def test_delete_all_with_conditions_is_deprecated -    assert_deprecated do -      assert_difference("Author.count", -1) { Author.delete_all(name: "David") } -    end -  end -    def test_delete_all_loaded      davids = Author.where(name: "David") @@ -1638,9 +1617,9 @@ class RelationTest < ActiveRecord::TestCase      assert_equal "David", topic2.reload.author_name    end -  def test_update_on_relation_passing_active_record_object_is_deprecated +  def test_update_on_relation_passing_active_record_object_is_not_permitted      topic = Topic.create!(title: "Foo", author_name: nil) -    assert_deprecated(/update/) do +    assert_raises(ArgumentError) do        Topic.where(id: topic.id).update(topic, title: "Bar")      end    end @@ -1654,17 +1633,11 @@ class RelationTest < ActiveRecord::TestCase      assert_equal ["Foo", "Foo"], query.map(&:name)      assert_sql(/DISTINCT/) do        assert_equal ["Foo"], query.distinct.map(&:name) -      assert_deprecated { assert_equal ["Foo"], query.uniq.map(&:name) }      end      assert_sql(/DISTINCT/) do        assert_equal ["Foo"], query.distinct(true).map(&:name) -      assert_deprecated { assert_equal ["Foo"], query.uniq(true).map(&:name) }      end      assert_equal ["Foo", "Foo"], query.distinct(true).distinct(false).map(&:name) - -    assert_deprecated do -      assert_equal ["Foo", "Foo"], query.uniq(true).uniq(false).map(&:name) -    end    end    def test_doesnt_add_having_values_if_options_are_blank diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index bea78d2a95..34c5f356b8 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -343,7 +343,7 @@ class SchemaDumperTest < ActiveRecord::TestCase          t.column :name, :string          t.column :owner_id, :bigint          t.index [:name] -        t.foreign_key :dog_owners, column: "owner_id" if supports_foreign_keys? +        t.foreign_key :dog_owners, column: "owner_id"        end      end      def down diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb index b408a6000b..3a04f4bf7d 100644 --- a/activerecord/test/cases/scoping/default_scoping_test.rb +++ b/activerecord/test/cases/scoping/default_scoping_test.rb @@ -5,6 +5,7 @@ require "models/developer"  require "models/computer"  require "models/vehicle"  require "models/cat" +require "concurrent/atomic/cyclic_barrier"  class DefaultScopingTest < ActiveRecord::TestCase    fixtures :developers, :posts, :comments @@ -437,12 +438,17 @@ class DefaultScopingTest < ActiveRecord::TestCase        threads = []        assert_not_equal 1, ThreadsafeDeveloper.unscoped.count +      barrier_1 = Concurrent::CyclicBarrier.new(2) +      barrier_2 = Concurrent::CyclicBarrier.new(2) +        threads << Thread.new do -        Thread.current[:long_default_scope] = true +        Thread.current[:default_scope_delay] = -> { barrier_1.wait; barrier_2.wait }          assert_equal 1, ThreadsafeDeveloper.all.to_a.count          ThreadsafeDeveloper.connection.close        end        threads << Thread.new do +        Thread.current[:default_scope_delay] = -> { barrier_2.wait } +        barrier_1.wait          assert_equal 1, ThreadsafeDeveloper.all.to_a.count          ThreadsafeDeveloper.connection.close        end diff --git a/activerecord/test/cases/secure_token_test.rb b/activerecord/test/cases/secure_token_test.rb index eda0229c26..7b9cbee40a 100644 --- a/activerecord/test/cases/secure_token_test.rb +++ b/activerecord/test/cases/secure_token_test.rb @@ -27,6 +27,6 @@ class SecureTokenTest < ActiveRecord::TestCase      @user.token = "custom-secure-token"      @user.save -    assert_equal @user.token, "custom-secure-token" +    assert_equal "custom-secure-token", @user.token    end  end diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb index 1ccbf3ed4a..673392b4c4 100644 --- a/activerecord/test/cases/serialized_attribute_test.rb +++ b/activerecord/test/cases/serialized_attribute_test.rb @@ -185,14 +185,14 @@ class SerializedAttributeTest < ActiveRecord::TestCase      topic = Topic.new(content: true)      assert topic.save      topic = topic.reload -    assert_equal topic.content, true +    assert_equal true, topic.content    end    def test_serialized_boolean_value_false      topic = Topic.new(content: false)      assert topic.save      topic = topic.reload -    assert_equal topic.content, false +    assert_equal false, topic.content    end    def test_serialize_with_coder @@ -211,7 +211,7 @@ class SerializedAttributeTest < ActiveRecord::TestCase      topic.save!      topic.reload      assert_kind_of some_class, topic.content -    assert_equal topic.content, some_class.new("my value") +    assert_equal some_class.new("my value"), topic.content    end    def test_serialize_attribute_via_select_method_when_time_zone_available @@ -240,6 +240,20 @@ class SerializedAttributeTest < ActiveRecord::TestCase      assert_equal [], light.long_state    end +  def test_unexpected_serialized_type +    Topic.serialize :content, Hash +    topic = Topic.create!(content: { zomg: true }) + +    Topic.serialize :content, Array + +    topic.reload +    error = assert_raise(ActiveRecord::SerializationTypeMismatch) do +      topic.content +    end +    expected = "can't load `content`: was supposed to be a Array, but was a Hash. -- {:zomg=>true}" +    assert_equal expected, error.to_s +  end +    def test_serialized_column_should_unserialize_after_update_column      t = Topic.create(content: "first")      assert_equal("first", t.content) diff --git a/activerecord/test/cases/tasks/database_tasks_test.rb b/activerecord/test/cases/tasks/database_tasks_test.rb index d03231e711..5653fd83fd 100644 --- a/activerecord/test/cases/tasks/database_tasks_test.rb +++ b/activerecord/test/cases/tasks/database_tasks_test.rb @@ -61,7 +61,7 @@ module ActiveRecord        instance = klazz.new        klazz.stubs(:new).returns instance -      instance.expects(:structure_dump).with("awesome-file.sql") +      instance.expects(:structure_dump).with("awesome-file.sql", nil)        ActiveRecord::Tasks::DatabaseTasks.register_task(/foo/, klazz)        ActiveRecord::Tasks::DatabaseTasks.structure_dump({ "adapter" => :foo }, "awesome-file.sql") @@ -85,11 +85,23 @@ module ActiveRecord      end    end +  class DatabaseTasksDumpSchemaCacheTest < ActiveRecord::TestCase +    def test_dump_schema_cache +      path = "/tmp/my_schema_cache.yml" +      ActiveRecord::Tasks::DatabaseTasks.dump_schema_cache(ActiveRecord::Base.connection, path) +      assert File.file?(path) +    ensure +      FileUtils.rm_rf(path) +    end +  end +    class DatabaseTasksCreateAllTest < ActiveRecord::TestCase      def setup        @configurations = { "development" => { "database" => "my-db" } }        ActiveRecord::Base.stubs(:configurations).returns(@configurations) +      # To refrain from connecting to a newly created empty DB in sqlite3_mem tests +      ActiveRecord::Base.connection_handler.stubs(:establish_connection)      end      def test_ignores_configurations_without_databases @@ -411,7 +423,7 @@ module ActiveRecord      ADAPTERS_TASKS.each do |k, v|        define_method("test_#{k}_structure_dump") do -        eval("@#{v}").expects(:structure_dump).with("awesome-file.sql") +        eval("@#{v}").expects(:structure_dump).with("awesome-file.sql", nil)          ActiveRecord::Tasks::DatabaseTasks.structure_dump({ "adapter" => k }, "awesome-file.sql")        end      end @@ -422,7 +434,7 @@ module ActiveRecord      ADAPTERS_TASKS.each do |k, v|        define_method("test_#{k}_structure_load") do -        eval("@#{v}").expects(:structure_load).with("awesome-file.sql") +        eval("@#{v}").expects(:structure_load).with("awesome-file.sql", nil)          ActiveRecord::Tasks::DatabaseTasks.structure_load({ "adapter" => k }, "awesome-file.sql")        end      end diff --git a/activerecord/test/cases/tasks/mysql_rake_test.rb b/activerecord/test/cases/tasks/mysql_rake_test.rb index dbe935808e..f30e0958c3 100644 --- a/activerecord/test/cases/tasks/mysql_rake_test.rb +++ b/activerecord/test/cases/tasks/mysql_rake_test.rb @@ -59,7 +59,7 @@ if current_adapter?(:Mysql2Adapter)        def test_when_database_created_successfully_outputs_info_to_stdout          ActiveRecord::Tasks::DatabaseTasks.create @configuration -        assert_equal $stdout.string, "Created database 'my-app-db'\n" +        assert_equal "Created database 'my-app-db'\n", $stdout.string        end        def test_create_when_database_exists_outputs_info_to_stderr @@ -69,7 +69,7 @@ if current_adapter?(:Mysql2Adapter)          ActiveRecord::Tasks::DatabaseTasks.create @configuration -        assert_equal $stderr.string, "Database 'my-app-db' already exists\n" +        assert_equal "Database 'my-app-db' already exists\n", $stderr.string        end      end @@ -205,7 +205,7 @@ if current_adapter?(:Mysql2Adapter)        def test_when_database_dropped_successfully_outputs_info_to_stdout          ActiveRecord::Tasks::DatabaseTasks.drop @configuration -        assert_equal $stdout.string, "Dropped database 'my-app-db'\n" +        assert_equal "Dropped database 'my-app-db'\n", $stdout.string        end      end @@ -294,6 +294,17 @@ if current_adapter?(:Mysql2Adapter)          ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename)        end +      def test_structure_dump_with_extra_flags +        filename = "awesome-file.sql" +        expected_command = ["mysqldump", "--result-file", filename, "--no-data", "--routines", "--skip-comments", "--noop", "test-db"] + +        assert_called_with(Kernel, :system, expected_command, returns: true) do +          with_structure_dump_flags(["--noop"]) do +            ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, filename) +          end +        end +      end +        def test_warn_when_external_structure_dump_command_execution_fails          filename = "awesome-file.sql"          Kernel.expects(:system) @@ -323,6 +334,15 @@ if current_adapter?(:Mysql2Adapter)            @configuration.merge("sslca" => "ca.crt"),            filename)        end + +      private +        def with_structure_dump_flags(flags) +          old = ActiveRecord::Tasks::DatabaseTasks.structure_dump_flags +          ActiveRecord::Tasks::DatabaseTasks.structure_dump_flags = flags +          yield +        ensure +          ActiveRecord::Tasks::DatabaseTasks.structure_dump_flags = old +        end      end      class MySQLStructureLoadTest < ActiveRecord::TestCase @@ -335,11 +355,23 @@ if current_adapter?(:Mysql2Adapter)        def test_structure_load          filename = "awesome-file.sql" -        Kernel.expects(:system).with("mysql", "--execute", %{SET FOREIGN_KEY_CHECKS = 0; SOURCE #{filename}; SET FOREIGN_KEY_CHECKS = 1}, "--database", "test-db") -          .returns(true) +        expected_command = ["mysql", "--execute", %{SET FOREIGN_KEY_CHECKS = 0; SOURCE #{filename}; SET FOREIGN_KEY_CHECKS = 1}, "--database", "test-db", "--noop"] -        ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename) +        assert_called_with(Kernel, :system, expected_command, returns: true) do +          with_structure_load_flags(["--noop"]) do +            ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename) +          end +        end        end + +      private +        def with_structure_load_flags(flags) +          old = ActiveRecord::Tasks::DatabaseTasks.structure_load_flags +          ActiveRecord::Tasks::DatabaseTasks.structure_load_flags = flags +          yield +        ensure +          ActiveRecord::Tasks::DatabaseTasks.structure_load_flags = old +        end      end    end  end diff --git a/activerecord/test/cases/tasks/postgresql_rake_test.rb b/activerecord/test/cases/tasks/postgresql_rake_test.rb index b8c8ec88f0..a23100c32a 100644 --- a/activerecord/test/cases/tasks/postgresql_rake_test.rb +++ b/activerecord/test/cases/tasks/postgresql_rake_test.rb @@ -74,7 +74,7 @@ if current_adapter?(:PostgreSQLAdapter)        def test_when_database_created_successfully_outputs_info_to_stdout          ActiveRecord::Tasks::DatabaseTasks.create @configuration -        assert_equal $stdout.string, "Created database 'my-app-db'\n" +        assert_equal "Created database 'my-app-db'\n", $stdout.string        end        def test_create_when_database_exists_outputs_info_to_stderr @@ -84,7 +84,7 @@ if current_adapter?(:PostgreSQLAdapter)          ActiveRecord::Tasks::DatabaseTasks.create @configuration -        assert_equal $stderr.string, "Database 'my-app-db' already exists\n" +        assert_equal "Database 'my-app-db' already exists\n", $stderr.string        end      end @@ -126,7 +126,7 @@ if current_adapter?(:PostgreSQLAdapter)        def test_when_database_dropped_successfully_outputs_info_to_stdout          ActiveRecord::Tasks::DatabaseTasks.drop @configuration -        assert_equal $stdout.string, "Dropped database 'my-app-db'\n" +        assert_equal "Dropped database 'my-app-db'\n", $stdout.string        end      end @@ -236,6 +236,16 @@ if current_adapter?(:PostgreSQLAdapter)          ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, @filename)        end +      def test_structure_dump_with_extra_flags +        expected_command = ["pg_dump", "-s", "-x", "-O", "-f", @filename, "--noop", "my-app-db"] + +        assert_called_with(Kernel, :system, expected_command, returns: true) do +          with_structure_dump_flags(["--noop"]) do +            ActiveRecord::Tasks::DatabaseTasks.structure_dump(@configuration, @filename) +          end +        end +      end +        def test_structure_dump_with_schema_search_path          @configuration["schema_search_path"] = "foo,bar" @@ -263,7 +273,6 @@ if current_adapter?(:PostgreSQLAdapter)        end        private -          def with_dump_schemas(value, &block)            old_dump_schemas = ActiveRecord::Base.dump_schemas            ActiveRecord::Base.dump_schemas = value @@ -271,6 +280,14 @@ if current_adapter?(:PostgreSQLAdapter)          ensure            ActiveRecord::Base.dump_schemas = old_dump_schemas          end + +        def with_structure_dump_flags(flags) +          old = ActiveRecord::Tasks::DatabaseTasks.structure_dump_flags +          ActiveRecord::Tasks::DatabaseTasks.structure_dump_flags = flags +          yield +        ensure +          ActiveRecord::Tasks::DatabaseTasks.structure_dump_flags = old +        end      end      class PostgreSQLStructureLoadTest < ActiveRecord::TestCase @@ -293,12 +310,32 @@ if current_adapter?(:PostgreSQLAdapter)          ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)        end +      def test_structure_load_with_extra_flags +        filename = "awesome-file.sql" +        expected_command = ["psql", "-v", "ON_ERROR_STOP=1", "-q", "-f", filename, "--noop", @configuration["database"]] + +        assert_called_with(Kernel, :system, expected_command, returns: true) do +          with_structure_load_flags(["--noop"]) do +            ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename) +          end +        end +      end +        def test_structure_load_accepts_path_with_spaces          filename = "awesome file.sql"          Kernel.expects(:system).with("psql", "-v", "ON_ERROR_STOP=1", "-q", "-f", filename, @configuration["database"]).returns(true)          ActiveRecord::Tasks::DatabaseTasks.structure_load(@configuration, filename)        end + +      private +        def with_structure_load_flags(flags) +          old = ActiveRecord::Tasks::DatabaseTasks.structure_load_flags +          ActiveRecord::Tasks::DatabaseTasks.structure_load_flags = flags +          yield +        ensure +          ActiveRecord::Tasks::DatabaseTasks.structure_load_flags = old +        end      end    end  end diff --git a/activerecord/test/cases/tasks/sqlite_rake_test.rb b/activerecord/test/cases/tasks/sqlite_rake_test.rb index 141048bfe7..0d917f3f6c 100644 --- a/activerecord/test/cases/tasks/sqlite_rake_test.rb +++ b/activerecord/test/cases/tasks/sqlite_rake_test.rb @@ -34,7 +34,7 @@ if current_adapter?(:SQLite3Adapter)        def test_when_db_created_successfully_outputs_info_to_stdout          ActiveRecord::Tasks::DatabaseTasks.create @configuration, "/rails/root" -        assert_equal $stdout.string, "Created database '#{@database}'\n" +        assert_equal "Created database '#{@database}'\n", $stdout.string        end        def test_db_create_when_file_exists @@ -42,7 +42,7 @@ if current_adapter?(:SQLite3Adapter)          ActiveRecord::Tasks::DatabaseTasks.create @configuration, "/rails/root" -        assert_equal $stderr.string, "Database '#{@database}' already exists\n" +        assert_equal "Database '#{@database}' already exists\n", $stderr.string        end        def test_db_create_with_file_does_nothing @@ -128,7 +128,7 @@ if current_adapter?(:SQLite3Adapter)        def test_when_db_dropped_successfully_outputs_info_to_stdout          ActiveRecord::Tasks::DatabaseTasks.drop @configuration, "/rails/root" -        assert_equal $stdout.string, "Dropped database '#{@database}'\n" +        assert_equal "Dropped database '#{@database}'\n", $stdout.string        end      end diff --git a/activerecord/test/cases/test_fixtures_test.rb b/activerecord/test/cases/test_fixtures_test.rb index 7090202a89..58d3bea3a2 100644 --- a/activerecord/test/cases/test_fixtures_test.rb +++ b/activerecord/test/cases/test_fixtures_test.rb @@ -6,25 +6,7 @@ class TestFixturesTest < ActiveRecord::TestCase      @klass.include(ActiveRecord::TestFixtures)    end -  def test_deprecated_use_transactional_fixtures= -    assert_deprecated "use use_transactional_tests= instead" do -      @klass.use_transactional_fixtures = true -    end -  end - -  def test_use_transactional_tests_prefers_use_transactional_fixtures -    ActiveSupport::Deprecation.silence do -      @klass.use_transactional_fixtures = false -    end - -    assert_equal false, @klass.use_transactional_tests -  end -    def test_use_transactional_tests_defaults_to_true -    ActiveSupport::Deprecation.silence do -      @klass.use_transactional_fixtures = nil -    end -      assert_equal true, @klass.use_transactional_tests    end diff --git a/activerecord/test/cases/time_precision_test.rb b/activerecord/test/cases/time_precision_test.rb index 03f6c234e8..09c585167e 100644 --- a/activerecord/test/cases/time_precision_test.rb +++ b/activerecord/test/cases/time_precision_test.rb @@ -68,7 +68,7 @@ if subsecond_precision_supported?        assert_match %r{t\.time\s+"finish",\s+precision: 6$}, output      end -    if current_adapter?(:PostgreSQLAdapter) +    if current_adapter?(:PostgreSQLAdapter, :SQLServerAdapter)        def test_time_precision_with_zero_should_be_dumped          @connection.create_table(:foos, force: true) do |t|            t.time :start,  precision: 0 diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb index cd83518e84..39b40e3411 100644 --- a/activerecord/test/cases/timestamp_test.rb +++ b/activerecord/test/cases/timestamp_test.rb @@ -422,7 +422,7 @@ class TimestampTest < ActiveRecord::TestCase        self.table_name = "people"        before_create do -        self.born_at = self.created_at +        self.born_at = created_at        end      end @@ -430,34 +430,19 @@ class TimestampTest < ActiveRecord::TestCase      assert_not_equal person.born_at, nil    end -  def test_timestamp_attributes_for_create -    toy = Toy.first -    assert_equal [:created_at, :created_on], toy.send(:timestamp_attributes_for_create) -  end - -  def test_timestamp_attributes_for_update -    toy = Toy.first -    assert_equal [:updated_at, :updated_on], toy.send(:timestamp_attributes_for_update) -  end - -  def test_all_timestamp_attributes -    toy = Toy.first -    assert_equal [:created_at, :created_on, :updated_at, :updated_on], toy.send(:all_timestamp_attributes) -  end -    def test_timestamp_attributes_for_create_in_model      toy = Toy.first -    assert_equal [:created_at], toy.send(:timestamp_attributes_for_create_in_model) +    assert_equal ["created_at"], toy.send(:timestamp_attributes_for_create_in_model)    end    def test_timestamp_attributes_for_update_in_model      toy = Toy.first -    assert_equal [:updated_at], toy.send(:timestamp_attributes_for_update_in_model) +    assert_equal ["updated_at"], toy.send(:timestamp_attributes_for_update_in_model)    end    def test_all_timestamp_attributes_in_model      toy = Toy.first -    assert_equal [:created_at, :updated_at], toy.send(:all_timestamp_attributes_in_model) +    assert_equal ["created_at", "updated_at"], toy.send(:all_timestamp_attributes_in_model)    end    def test_index_is_created_for_both_timestamps diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb index 9b1cca8583..8f9980f168 100644 --- a/activerecord/test/cases/transactions_test.rb +++ b/activerecord/test/cases/transactions_test.rb @@ -279,7 +279,11 @@ class TransactionTest < ActiveRecord::TestCase        e = assert_raises(RuntimeError) { new_topic.save }        assert_equal "Make the transaction rollback", e.message        assert_equal new_record_snapshot, !new_topic.persisted?, "The topic should have its old persisted value" -      assert_equal id_snapshot, new_topic.id, "The topic should have its old id" +      if id_snapshot.nil? +        assert_nil new_topic.id, "The topic should have its old id" +      else +        assert_equal id_snapshot, new_topic.id, "The topic should have its old id" +      end        assert_equal id_present, new_topic.has_attribute?(Topic.primary_key)      end    end diff --git a/activerecord/test/cases/view_test.rb b/activerecord/test/cases/view_test.rb index 1f326d4b39..07288568e8 100644 --- a/activerecord/test/cases/view_test.rb +++ b/activerecord/test/cases/view_test.rb @@ -45,8 +45,7 @@ module ViewBehavior    def test_table_exists      view_name = Ebook.table_name -    # TODO: switch this assertion around once we changed #tables to not return views. -    ActiveSupport::Deprecation.silence { assert @connection.table_exists?(view_name), "'#{view_name}' table should exist" } +    assert_not @connection.table_exists?(view_name), "'#{view_name}' table should not exist"    end    def test_views_ara_valid_data_sources @@ -131,8 +130,7 @@ if ActiveRecord::Base.connection.supports_views?      def test_table_exists        view_name = Paperback.table_name -      # TODO: switch this assertion around once we changed #tables to not return views. -      ActiveSupport::Deprecation.silence { assert @connection.table_exists?(view_name), "'#{view_name}' table should exist" } +      assert_not @connection.table_exists?(view_name), "'#{view_name}' table should not exist"      end      def test_column_definitions @@ -156,7 +154,7 @@ if ActiveRecord::Base.connection.supports_views?    end    # sqlite dose not support CREATE, INSERT, and DELETE for VIEW -  if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter) +  if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter, :SQLServerAdapter)      class UpdateableViewTest < ActiveRecord::TestCase        self.use_transactional_tests = false        fixtures :books @@ -202,8 +200,8 @@ if ActiveRecord::Base.connection.supports_views?          end        end      end -  end # end fo `if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter)` -end # end fo `if ActiveRecord::Base.connection.supports_views?` +  end # end of `if current_adapter?(:Mysql2Adapter, :PostgreSQLAdapter, :SQLServerAdapter)` +end # end of `if ActiveRecord::Base.connection.supports_views?`  if ActiveRecord::Base.connection.respond_to?(:supports_materialized_views?) &&      ActiveRecord::Base.connection.supports_materialized_views? diff --git a/activerecord/test/fixtures/subscribers.yml b/activerecord/test/fixtures/subscribers.yml index c6a8c2fa24..0f6e0cd48e 100644 --- a/activerecord/test/fixtures/subscribers.yml +++ b/activerecord/test/fixtures/subscribers.yml @@ -6,6 +6,6 @@ second:    nick: webster132    name: David Heinemeier Hansson -thrid: +third:    nick: swistak    name: Marcin Raczkowski
\ No newline at end of file diff --git a/activerecord/test/models/boolean.rb b/activerecord/test/models/boolean.rb index 7bae22e5f9..0da228aac2 100644 --- a/activerecord/test/models/boolean.rb +++ b/activerecord/test/models/boolean.rb @@ -1,2 +1,5 @@  class Boolean < ActiveRecord::Base +  def has_fun +    super +  end  end diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb index 4561b3132b..20e37710e7 100644 --- a/activerecord/test/models/company.rb +++ b/activerecord/test/models/company.rb @@ -216,14 +216,12 @@ class Account < ActiveRecord::Base    validate :check_empty_credit_limit -  protected +  private      def check_empty_credit_limit        errors.add("credit_limit", :blank) if credit_limit.blank?      end -  private -      def private_method        "Sir, yes sir!"      end diff --git a/activerecord/test/models/customer.rb b/activerecord/test/models/customer.rb index 60af7c2247..3d40cb1ace 100644 --- a/activerecord/test/models/customer.rb +++ b/activerecord/test/models/customer.rb @@ -56,7 +56,7 @@ class GpsLocation    end    def ==(other) -    self.latitude == other.latitude && self.longitude == other.longitude +    latitude == other.latitude && longitude == other.longitude    end  end diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb index 5ca1d37f6d..654830ba11 100644 --- a/activerecord/test/models/developer.rb +++ b/activerecord/test/models/developer.rb @@ -251,7 +251,7 @@ class ThreadsafeDeveloper < ActiveRecord::Base    self.table_name = "developers"    def self.default_scope -    sleep 0.05 if Thread.current[:long_default_scope] +    Thread.current[:default_scope_delay].call      limit(1)    end  end @@ -260,3 +260,9 @@ class CachedDeveloper < ActiveRecord::Base    self.table_name = "developers"    self.cache_timestamp_format = :number  end + +class DeveloperWithIncorrectlyOrderedHasManyThrough < ActiveRecord::Base +  self.table_name = "developers" +  has_many :companies, through: :contracts +  has_many :contracts, foreign_key: :developer_id +end diff --git a/activerecord/test/models/family.rb b/activerecord/test/models/family.rb new file mode 100644 index 0000000000..5ae5a78c95 --- /dev/null +++ b/activerecord/test/models/family.rb @@ -0,0 +1,4 @@ +class Family < ActiveRecord::Base +  has_many :family_trees, -> { where(token: nil) } +  has_many :members, through: :family_trees +end diff --git a/activerecord/test/models/family_tree.rb b/activerecord/test/models/family_tree.rb new file mode 100644 index 0000000000..cd9829fedd --- /dev/null +++ b/activerecord/test/models/family_tree.rb @@ -0,0 +1,4 @@ +class FamilyTree < ActiveRecord::Base +  belongs_to :member, class_name: "User", foreign_key: "member_id" +  belongs_to :family +end diff --git a/activerecord/test/models/parrot.rb b/activerecord/test/models/parrot.rb index 5b693664d4..1e5f9285a8 100644 --- a/activerecord/test/models/parrot.rb +++ b/activerecord/test/models/parrot.rb @@ -13,6 +13,11 @@ class Parrot < ActiveRecord::Base    def cancel_save_callback_method      throw(:abort)    end + +  before_update :increment_updated_count +  def increment_updated_count +    self.updated_count += 1 +  end  end  class LiveParrot < Parrot diff --git a/activerecord/test/models/project.rb b/activerecord/test/models/project.rb index 5009f8f54b..4fbd986e40 100644 --- a/activerecord/test/models/project.rb +++ b/activerecord/test/models/project.rb @@ -11,7 +11,7 @@ class Project < ActiveRecord::Base                              after_add: Proc.new { |o, r| o.developers_log << "after_adding#{r.id || '<new>'}" },                              before_remove: Proc.new { |o, r| o.developers_log << "before_removing#{r.id}" },                              after_remove: Proc.new { |o, r| o.developers_log << "after_removing#{r.id}" } -  has_and_belongs_to_many :well_payed_salary_groups, -> { group("developers.salary").having("SUM(salary) > 10000").select("SUM(salary) as salary") }, class_name: "Developer" +  has_and_belongs_to_many :well_paid_salary_groups, -> { group("developers.salary").having("SUM(salary) > 10000").select("SUM(salary) as salary") }, class_name: "Developer"    belongs_to :firm    has_one :lead_developer, through: :firm, inverse_of: :contracted_projects diff --git a/activerecord/test/models/user.rb b/activerecord/test/models/user.rb index 47649e0a77..5089a795f4 100644 --- a/activerecord/test/models/user.rb +++ b/activerecord/test/models/user.rb @@ -5,8 +5,12 @@ class User < ActiveRecord::Base    has_secure_token :auth_token    has_and_belongs_to_many :jobs_pool, -    class_name: Job, +    class_name: "Job",      join_table: "jobs_pool" + +  has_one :family_tree, -> { where(token: nil) }, foreign_key: "member_id" +  has_one :family, through: :family_tree +  has_many :family_members, through: :family, source: :members  end  class UserWithNotification < User diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 9d76d0537e..4c31548fff 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -329,6 +329,15 @@ ActiveRecord::Schema.define do    create_table :eyes, force: true do |t|    end +  create_table :families, force: true do |t| +  end + +  create_table :family_trees, force: true do |t| +    t.references :family +    t.references :member +    t.string :token +  end +    create_table :funny_jokes, force: true do |t|      t.string :name    end @@ -574,6 +583,7 @@ ActiveRecord::Schema.define do      t.column :color, :string      t.column :parrot_sti_class, :string      t.column :killer_id, :integer +    t.column :updated_count, :integer, default: 0      if subsecond_precision_supported?        t.column :created_at, :datetime, precision: 0        t.column :created_on, :datetime, precision: 0 @@ -1004,16 +1014,14 @@ ActiveRecord::Schema.define do    create_table :records, force: true do |t|    end -  if supports_foreign_keys? -    # fk_test_has_fk should be before fk_test_has_pk -    create_table :fk_test_has_fk, force: true do |t| -      t.bigint :fk_id, null: false +  disable_referential_integrity do +    create_table :fk_test_has_pk, primary_key: "pk_id", force: :cascade do |t|      end -    create_table :fk_test_has_pk, force: true, primary_key: "pk_id" do |t| +    create_table :fk_test_has_fk, force: true do |t| +      t.references :fk, null: false +      t.foreign_key :fk_test_has_pk, column: "fk_id", name: "fk_name", primary_key: "pk_id"      end - -    add_foreign_key :fk_test_has_fk, :fk_test_has_pk, column: "fk_id", name: "fk_name", primary_key: "pk_id"    end    create_table :overloaded_types, force: true do |t| diff --git a/activerecord/test/schema/sqlite_specific_schema.rb b/activerecord/test/schema/sqlite_specific_schema.rb deleted file mode 100644 index cc7c36fe2b..0000000000 --- a/activerecord/test/schema/sqlite_specific_schema.rb +++ /dev/null @@ -1,18 +0,0 @@ -ActiveRecord::Schema.define do -  execute "DROP TABLE fk_test_has_fk" rescue nil -  execute "DROP TABLE fk_test_has_pk" rescue nil -  execute <<_SQL -  CREATE TABLE 'fk_test_has_pk' ( -    'pk_id' INTEGER NOT NULL PRIMARY KEY -  ); -_SQL - -  execute <<_SQL -  CREATE TABLE 'fk_test_has_fk' ( -    'id'    INTEGER NOT NULL PRIMARY KEY, -    'fk_id' INTEGER NOT NULL, - -    FOREIGN KEY ('fk_id') REFERENCES 'fk_test_has_pk'('pk_id') -  ); -_SQL -end diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 873a39dbf6..5af97e3d37 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,54 @@ +*   Updated Unicode version to 9.0.0 + +    Now we can handle new emojis such like "👩👩👧👦" ("\u{1F469}\u{200D}\u{1F469}\u{200D}\u{1F467}\u{200D}\u{1F466}"). + +    version 8.0.0 + +        "👩👩👧👦".mb_chars.grapheme_length # => 4 +        "👩👩👧👦".mb_chars.reverse # => "👦👧👩👩" + +    version 9.0.0 + +        "👩👩👧👦".mb_chars.grapheme_length # => 1 +        "👩👩👧👦".mb_chars.reverse # => "👩👩👧👦" + +    *Fumiaki MATSUSHIMA* + +*   Changed `ActiveSupport::Inflector#transliterate` to raise `ArgumentError` when it receives +    anything except a string. + +    *Kevin McPhillips* + +*   Fixed bugs that `StringInquirer#respond_to_missing?` and +    `ArrayInquirer#respond_to_missing?` do not fallback to `super`. + +    *Akira Matsuda* + +*   Fix inconsistent results when parsing large durations and constructing durations from code + +        ActiveSupport::Duration.parse('P3Y') == 3.years # It should be true + +    Duration parsing made independent from any moment of time: +    Fixed length in seconds is assigned to each duration part during parsing. + +    Changed duration of months and years in seconds to more accurate and logical: + +     1. The value of 365.2425 days in Gregorian year is more accurate +        as it accounts for every 400th non-leap year. + +     2. Month's length is bound to year's duration, which makes +        sensible comparisons like `12.months == 1.year` to be `true` +        and nonsensical ones like `30.days == 1.month` to be `false`. + +    Calculations on times and dates with durations shouldn't be affected as +    duration's numeric value isn't used in calculations, only parts are used. + +    Methods on `Numeric` like `2.days` now use these predefined durations +    to avoid duplicating of duration constants through the codebase and +    eliminate creation of intermediate durations. + +    *Andrey Novikov, Andrew White* +  *   Change return value of `Rational#duplicable?`, `ComplexClass#duplicable?`      to false. diff --git a/activesupport/MIT-LICENSE b/activesupport/MIT-LICENSE index 40235833ba..6b3cead1a7 100644 --- a/activesupport/MIT-LICENSE +++ b/activesupport/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2005-2016 David Heinemeier Hansson +Copyright (c) 2005-2017 David Heinemeier Hansson  Permission is hereby granted, free of charge, to any person obtaining  a copy of this software and associated documentation files (the diff --git a/activesupport/bin/generate_tables b/activesupport/bin/generate_tables index 5d912f375c..aa36a01b5b 100755 --- a/activesupport/bin/generate_tables +++ b/activesupport/bin/generate_tables @@ -8,6 +8,7 @@ end  require "open-uri"  require "tmpdir" +require "fileutils"  module ActiveSupport    module Multibyte @@ -101,9 +102,10 @@ module ActiveSupport          def parse            SOURCES.each do |type, url| -            filename = File.join(Dir.tmpdir, "#{url.split('/').last}") +            filename = File.join(Dir.tmpdir, UNICODE_VERSION, "#{url.split('/').last}")              unless File.exist?(filename)                $stderr.puts "Downloading #{url.split('/').last}" +              FileUtils.mkdir_p(File.dirname(filename))                File.open(filename, "wb") do |target|                  open(url) do |source|                    source.each_line { |line| target.write line } diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb index 2fc42e1975..267fa755c6 100644 --- a/activesupport/lib/active_support.rb +++ b/activesupport/lib/active_support.rb @@ -1,5 +1,5 @@  #-- -# Copyright (c) 2005-2016 David Heinemeier Hansson +# Copyright (c) 2005-2017 David Heinemeier Hansson  #  # Permission is hereby granted, free of charge, to any person obtaining  # a copy of this software and associated documentation files (the diff --git a/activesupport/lib/active_support/array_inquirer.rb b/activesupport/lib/active_support/array_inquirer.rb index 85122e39b2..befa1746c6 100644 --- a/activesupport/lib/active_support/array_inquirer.rb +++ b/activesupport/lib/active_support/array_inquirer.rb @@ -20,7 +20,7 @@ module ActiveSupport      #   variants.any?(:phone, :tablet)     # => true      #   variants.any?('phone', 'desktop')  # => true      #   variants.any?(:desktop, :watch)    # => false -    def any?(*candidates, &block) +    def any?(*candidates)        if candidates.none?          super        else @@ -32,7 +32,7 @@ module ActiveSupport      private        def respond_to_missing?(name, include_private = false) -        name[-1] == "?" +        (name[-1] == "?") || super        end        def method_missing(name, *args) diff --git a/activesupport/lib/active_support/backtrace_cleaner.rb b/activesupport/lib/active_support/backtrace_cleaner.rb index 169a58ecd1..e47c90597f 100644 --- a/activesupport/lib/active_support/backtrace_cleaner.rb +++ b/activesupport/lib/active_support/backtrace_cleaner.rb @@ -12,7 +12,7 @@ module ActiveSupport    # is to exclude the output of a noisy library from the backtrace, so that you    # can focus on the rest.    # -  #   bc = BacktraceCleaner.new +  #   bc = ActiveSupport::BacktraceCleaner.new    #   bc.add_filter   { |line| line.gsub(Rails.root.to_s, '') } # strip the Rails.root prefix    #   bc.add_silencer { |line| line =~ /puma|rubygems/ } # skip any lines from puma or rubygems    #   bc.clean(exception.backtrace) # perform the cleanup diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index d1bbd2f405..4ff47c261d 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -71,8 +71,8 @@ module ActiveSupport        # each of elements in the array will be turned into parameters/keys and        # concatenated into a single key. For example:        # -      #   expand_cache_key([:foo, :bar])               # => "foo/bar" -      #   expand_cache_key([:foo, :bar], "namespace")  # => "namespace/foo/bar" +      #   ActiveSupport::Cache.expand_cache_key([:foo, :bar])               # => "foo/bar" +      #   ActiveSupport::Cache.expand_cache_key([:foo, :bar], "namespace")  # => "namespace/foo/bar"        #        # The +key+ argument can also respond to +cache_key+ or +to_param+.        def expand_cache_key(key, namespace = nil) diff --git a/activesupport/lib/active_support/core_ext/array/access.rb b/activesupport/lib/active_support/core_ext/array/access.rb index 37d833887a..fca33c9d69 100644 --- a/activesupport/lib/active_support/core_ext/array/access.rb +++ b/activesupport/lib/active_support/core_ext/array/access.rb @@ -31,7 +31,7 @@ class Array    #    #   people = ["David", "Rafael", "Aaron", "Todd"]    #   people.without "Aaron", "Todd" -  #     => ["David", "Rafael"] +  #   # => ["David", "Rafael"]    #    # Note: This is an optimization of `Enumerable#without` that uses `Array#-`    # instead of `Array#reject` for performance reasons. diff --git a/activesupport/lib/active_support/core_ext/array/grouping.rb b/activesupport/lib/active_support/core_ext/array/grouping.rb index ea9d85f6e3..0d798e5c4e 100644 --- a/activesupport/lib/active_support/core_ext/array/grouping.rb +++ b/activesupport/lib/active_support/core_ext/array/grouping.rb @@ -89,7 +89,7 @@ class Array    #   [1, 2, 3, 4, 5].split(3)              # => [[1, 2], [4, 5]]    #   (1..10).to_a.split { |i| i % 3 == 0 } # => [[1, 2], [4, 5], [7, 8], [10]]    def split(value = nil) -    arr = self.dup +    arr = dup      result = []      if block_given?        while (idx = arr.index { |i| yield i }) diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb index 2e64097933..d6f60cac04 100644 --- a/activesupport/lib/active_support/core_ext/date/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date/calculations.rb @@ -133,7 +133,7 @@ class Date    # Allow Date to be compared with Time by converting to DateTime and relying on the <=> from there.    def compare_with_coercion(other)      if other.is_a?(Time) -      self.to_datetime <=> other +      to_datetime <=> other      else        compare_without_coercion(other)      end diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb index 8ebe758078..90d7d2947f 100644 --- a/activesupport/lib/active_support/core_ext/enumerable.rb +++ b/activesupport/lib/active_support/core_ext/enumerable.rb @@ -12,7 +12,7 @@ module Enumerable    #    #  [5, 15, 10].sum # => 30    #  ['foo', 'bar'].sum # => "foobar" -  #  [[1, 2], [3, 1, 5]].sum => [1, 2, 3, 1, 5] +  #  [[1, 2], [3, 1, 5]].sum # => [1, 2, 3, 1, 5]    #    # The default sum of an empty list is zero. You can override this default:    # @@ -29,9 +29,9 @@ module Enumerable    # Convert an enumerable to a hash.    #    #   people.index_by(&:login) -  #     => { "nextangle" => <Person ...>, "chade-" => <Person ...>, ...} +  #   # => { "nextangle" => <Person ...>, "chade-" => <Person ...>, ...}    #   people.index_by { |person| "#{person.first_name} #{person.last_name}" } -  #     => { "Chade- Fowlersburg-e" => <Person ...>, "David Heinemeier Hansson" => <Person ...>, ...} +  #   # => { "Chade- Fowlersburg-e" => <Person ...>, "David Heinemeier Hansson" => <Person ...>, ...}    def index_by      if block_given?        result = {} @@ -67,10 +67,10 @@ module Enumerable    # Returns a copy of the enumerable without the specified elements.    #    #   ["David", "Rafael", "Aaron", "Todd"].without "Aaron", "Todd" -  #     => ["David", "Rafael"] +  #   # => ["David", "Rafael"]    #    #   {foo: 1, bar: 2, baz: 3}.without :bar -  #     => {foo: 1, baz: 3} +  #   # => {foo: 1, baz: 3}    def without(*elements)      reject { |element| elements.include?(element) }    end @@ -78,10 +78,10 @@ module Enumerable    # Convert an enumerable to an array based on the given key.    #    #   [{ name: "David" }, { name: "Rafael" }, { name: "Aaron" }].pluck(:name) -  #     => ["David", "Rafael", "Aaron"] +  #   # => ["David", "Rafael", "Aaron"]    #    #   [{ id: 1, name: "David" }, { id: 2, name: "Rafael" }].pluck(:id, :name) -  #     => [[1, "David"], [2, "Rafael"]] +  #   # => [[1, "David"], [2, "Rafael"]]    def pluck(*keys)      if keys.many?        map { |element| keys.map { |key| element[key] } } @@ -115,9 +115,14 @@ end  # and fall back to the compatible implementation, but that's much slower than  # just calling the compat method in the first place.  if Array.instance_methods(false).include?(:sum) && !(%w[a].sum rescue false) -  class Array -    alias :orig_sum :sum +  # Using Refinements here in order not to expose our internal method +  using Module.new { +    refine Array do +      alias :orig_sum :sum +    end +  } +  class Array      def sum(init = nil, &block) #:nodoc:        if init.is_a?(Numeric) || first.is_a?(Numeric)          init ||= 0 diff --git a/activesupport/lib/active_support/core_ext/integer/time.rb b/activesupport/lib/active_support/core_ext/integer/time.rb index 4a64872392..74baae3639 100644 --- a/activesupport/lib/active_support/core_ext/integer/time.rb +++ b/activesupport/lib/active_support/core_ext/integer/time.rb @@ -18,12 +18,12 @@ class Integer    #   # equivalent to Time.now.advance(months: 4, years: 5)    #   (4.months + 5.years).from_now    def months -    ActiveSupport::Duration.new(self * 30.days, [[:months, self]]) +    ActiveSupport::Duration.months(self)    end    alias :month :months    def years -    ActiveSupport::Duration.new(self * 365.25.days.to_i, [[:years, self]]) +    ActiveSupport::Duration.years(self)    end    alias :year :years  end diff --git a/activesupport/lib/active_support/core_ext/load_error.rb b/activesupport/lib/active_support/core_ext/load_error.rb index 3cf7ea0a97..d273487010 100644 --- a/activesupport/lib/active_support/core_ext/load_error.rb +++ b/activesupport/lib/active_support/core_ext/load_error.rb @@ -1,6 +1,3 @@ -require "active_support/deprecation" -require "active_support/deprecation/proxy_wrappers" -  class LoadError    REGEXPS = [      /^no such file to load -- (.+)$/i, @@ -9,18 +6,6 @@ class LoadError      /^cannot load such file -- (.+)$/i,    ] -  unless method_defined?(:path) -    # Returns the path which was unable to be loaded. -    def path -      @path ||= begin -        REGEXPS.find do |regex| -          message =~ regex -        end -        $1 -      end -    end -  end -    # Returns true if the given path name (except perhaps for the ".rb"    # extension) is the missing file which caused the exception to be raised.    def is_missing?(location) 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 90989f4f3d..2c24081eb9 100644 --- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb +++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb @@ -7,7 +7,8 @@ require "active_support/core_ext/regexp"  class Module    # Defines a class attribute and creates a class and instance reader methods.    # The underlying class variable is set to +nil+, if it is not previously -  # defined. +  # defined. All class and instance methods created will be public, even if +  # this method is called with a private or protected access modifier.    #    #   module HairColors    #     mattr_reader :hair_colors @@ -76,7 +77,9 @@ class Module    alias :cattr_reader :mattr_reader    # Defines a class attribute and creates a class and instance writer methods to -  # allow assignment to the attribute. +  # allow assignment to the attribute. All class and instance methods created +  # will be public, even if this method is called with a private or protected +  # access modifier.    #    #   module HairColors    #     mattr_writer :hair_colors @@ -142,6 +145,8 @@ class Module    alias :cattr_writer :mattr_writer    # Defines both class and instance accessors for class attributes. +  # All class and instance methods created will be public, even if +  # this method is called with a private or protected access modifier.    #    #   module HairColors    #     mattr_accessor :hair_colors diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb index 19f692e943..d82758e40d 100644 --- a/activesupport/lib/active_support/core_ext/module/delegation.rb +++ b/activesupport/lib/active_support/core_ext/module/delegation.rb @@ -20,7 +20,8 @@ class Module    # ==== Options    # * <tt>:to</tt> - Specifies the target object    # * <tt>:prefix</tt> - Prefixes the new method with the target name or a custom prefix -  # * <tt>:allow_nil</tt> - if set to true, prevents a +NoMethodError+ from being raised +  # * <tt>:allow_nil</tt> - if set to true, prevents a +Module::DelegationError+ +  #   from being raised    #    # The macro receives one or more method names (specified as symbols or    # strings) and the name of the target object via the <tt>:to</tt> option @@ -112,18 +113,19 @@ class Module    #   invoice.customer_address # => 'Vimmersvej 13'    #    # If the target is +nil+ and does not respond to the delegated method a -  # +NoMethodError+ is raised, as with any other value. Sometimes, however, it -  # makes sense to be robust to that situation and that is the purpose of the -  # <tt>:allow_nil</tt> option: If the target is not +nil+, or it is and -  # responds to the method, everything works as usual. But if it is +nil+ and -  # does not respond to the delegated method, +nil+ is returned. +  # +Module::DelegationError+ is raised, as with any other value. Sometimes, +  # however, it makes sense to be robust to that situation and that is the +  # purpose of the <tt>:allow_nil</tt> option: If the target is not +nil+, or it +  # is and responds to the method, everything works as usual. But if it is +nil+ +  # and does not respond to the delegated method, +nil+ is returned.    #    #   class User < ActiveRecord::Base    #     has_one :profile    #     delegate :age, to: :profile    #   end    # -  #   User.new.age # raises NoMethodError: undefined method `age' +  #   User.new.age +  #   # => Module::DelegationError: User#age delegated to profile.age, but profile is nil    #    # But if not having a profile yet is fine and should not be an error    # condition: diff --git a/activesupport/lib/active_support/core_ext/module/introspection.rb b/activesupport/lib/active_support/core_ext/module/introspection.rb index 0665aa88bc..ca20a6d4c5 100644 --- a/activesupport/lib/active_support/core_ext/module/introspection.rb +++ b/activesupport/lib/active_support/core_ext/module/introspection.rb @@ -5,10 +5,12 @@ class Module    #    #   M::N.parent_name # => "M"    def parent_name -    if defined? @parent_name +    if defined?(@parent_name)        @parent_name      else -      @parent_name = name =~ /::[^:]+\Z/ ? $`.freeze : nil +      parent_name = name =~ /::[^:]+\Z/ ? $`.freeze : nil +      @parent_name = parent_name unless frozen? +      parent_name      end    end diff --git a/activesupport/lib/active_support/core_ext/numeric/conversions.rb b/activesupport/lib/active_support/core_ext/numeric/conversions.rb index 946f8ddeab..4f6621693e 100644 --- a/activesupport/lib/active_support/core_ext/numeric/conversions.rb +++ b/activesupport/lib/active_support/core_ext/numeric/conversions.rb @@ -99,31 +99,30 @@ module ActiveSupport::NumericWithFormat    #  1234567.to_s(:human, precision: 1,    #                   separator: ',',    #                   significant: false)                   # => "1,2 Million" -  def to_s(*args) -    format, options = args -    options ||= {} - +  def to_s(format = nil, options = nil)      case format +    when nil +      super() +    when Integer, String +      super(format)      when :phone -      return ActiveSupport::NumberHelper.number_to_phone(self, options) +      return ActiveSupport::NumberHelper.number_to_phone(self, options || {})      when :currency -      return ActiveSupport::NumberHelper.number_to_currency(self, options) +      return ActiveSupport::NumberHelper.number_to_currency(self, options || {})      when :percentage -      return ActiveSupport::NumberHelper.number_to_percentage(self, options) +      return ActiveSupport::NumberHelper.number_to_percentage(self, options || {})      when :delimited -      return ActiveSupport::NumberHelper.number_to_delimited(self, options) +      return ActiveSupport::NumberHelper.number_to_delimited(self, options || {})      when :rounded -      return ActiveSupport::NumberHelper.number_to_rounded(self, options) +      return ActiveSupport::NumberHelper.number_to_rounded(self, options || {})      when :human -      return ActiveSupport::NumberHelper.number_to_human(self, options) +      return ActiveSupport::NumberHelper.number_to_human(self, options || {})      when :human_size -      return ActiveSupport::NumberHelper.number_to_human_size(self, options) +      return ActiveSupport::NumberHelper.number_to_human_size(self, options || {}) +    when Symbol +      super()      else -      if is_a?(Float) || format.is_a?(Symbol) -        super() -      else -        super -      end +      super(format)      end    end  end diff --git a/activesupport/lib/active_support/core_ext/numeric/time.rb b/activesupport/lib/active_support/core_ext/numeric/time.rb index 809dfd4e07..2e6c70d418 100644 --- a/activesupport/lib/active_support/core_ext/numeric/time.rb +++ b/activesupport/lib/active_support/core_ext/numeric/time.rb @@ -19,7 +19,7 @@ class Numeric    #   # equivalent to Time.current.advance(months: 4, years: 5)    #   (4.months + 5.years).from_now    def seconds -    ActiveSupport::Duration.new(self, [[:seconds, self]]) +    ActiveSupport::Duration.seconds(self)    end    alias :second :seconds @@ -27,7 +27,7 @@ class Numeric    #    #   2.minutes # => 2 minutes    def minutes -    ActiveSupport::Duration.new(self * 60, [[:minutes, self]]) +    ActiveSupport::Duration.minutes(self)    end    alias :minute :minutes @@ -35,7 +35,7 @@ class Numeric    #    #   2.hours # => 2 hours    def hours -    ActiveSupport::Duration.new(self * 3600, [[:hours, self]]) +    ActiveSupport::Duration.hours(self)    end    alias :hour :hours @@ -43,7 +43,7 @@ class Numeric    #    #   2.days # => 2 days    def days -    ActiveSupport::Duration.new(self * 24.hours, [[:days, self]]) +    ActiveSupport::Duration.days(self)    end    alias :day :days @@ -51,7 +51,7 @@ class Numeric    #    #   2.weeks # => 2 weeks    def weeks -    ActiveSupport::Duration.new(self * 7.days, [[:weeks, self]]) +    ActiveSupport::Duration.weeks(self)    end    alias :week :weeks @@ -59,7 +59,7 @@ class Numeric    #    #   2.fortnights # => 4 weeks    def fortnights -    ActiveSupport::Duration.new(self * 2.weeks, [[:weeks, self * 2]]) +    ActiveSupport::Duration.weeks(self * 2)    end    alias :fortnight :fortnights diff --git a/activesupport/lib/active_support/core_ext/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb index c671d34673..ea81df2bd8 100644 --- a/activesupport/lib/active_support/core_ext/object/duplicable.rb +++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb @@ -73,7 +73,8 @@ end  class Symbol    begin -    :symbol.dup +    :symbol.dup # Ruby 2.4.x. +    "symbol_from_string".to_sym.dup # Some symbols can't `dup` in Ruby 2.4.0.    rescue TypeError      # Symbols are not duplicable: diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb index 4eabce79e2..7a2fc5c1b5 100644 --- a/activesupport/lib/active_support/core_ext/string/inflections.rb +++ b/activesupport/lib/active_support/core_ext/string/inflections.rb @@ -128,10 +128,10 @@ class String    # Removes the module part from the constant expression in the string.    # -  #   'ActiveRecord::CoreExtensions::String::Inflections'.demodulize # => "Inflections" -  #   'Inflections'.demodulize                                       # => "Inflections" -  #   '::Inflections'.demodulize                                     # => "Inflections" -  #   ''.demodulize                                                  # => '' +  #   'ActiveSupport::Inflector::Inflections'.demodulize # => "Inflections" +  #   'Inflections'.demodulize                           # => "Inflections" +  #   '::Inflections'.demodulize                         # => "Inflections" +  #   ''.demodulize                                      # => ''    #    # See also +deconstantize+.    def demodulize 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 227c34b032..94ce3f6a61 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -152,18 +152,16 @@ module ActiveSupport #:nodoc:      def [](*args)        if args.size < 2          super -      else -        if html_safe? -          new_safe_buffer = super - -          if new_safe_buffer -            new_safe_buffer.instance_variable_set :@html_safe, true -          end +      elsif html_safe? +        new_safe_buffer = super -          new_safe_buffer -        else -          to_str[*args] +        if new_safe_buffer +          new_safe_buffer.instance_variable_set :@html_safe, true          end + +        new_safe_buffer +      else +        to_str[*args]        end      end diff --git a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb index 1c6618b19a..ce39e9a232 100644 --- a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb +++ b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb @@ -122,10 +122,11 @@ module ActiveSupport      #        (Backtrace information…)      #        ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"]      class DeprecatedConstantProxy < DeprecationProxy -      def initialize(old_const, new_const, deprecator = ActiveSupport::Deprecation.instance) +      def initialize(old_const, new_const, deprecator = ActiveSupport::Deprecation.instance, message: "#{old_const} is deprecated! Use #{new_const} instead.")          @old_const = old_const          @new_const = new_const          @deprecator = deprecator +        @message = message        end        # Returns the class of the new constant. @@ -143,7 +144,7 @@ module ActiveSupport          end          def warn(callstack, called, args) -          @deprecator.warn("#{@old_const} is deprecated! Use #{@new_const} instead.", callstack) +          @deprecator.warn(@message, callstack)          end      end    end diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb index c9e8c8fdc4..003f6203ef 100644 --- a/activesupport/lib/active_support/duration.rb +++ b/activesupport/lib/active_support/duration.rb @@ -7,13 +7,82 @@ module ActiveSupport    #    #   1.month.ago       # equivalent to Time.now.advance(months: -1)    class Duration -    EPOCH = ::Time.utc(2000) +    SECONDS_PER_MINUTE = 60 +    SECONDS_PER_HOUR   = 3600 +    SECONDS_PER_DAY    = 86400 +    SECONDS_PER_WEEK   = 604800 +    SECONDS_PER_MONTH  = 2629746  # 1/12 of a gregorian year +    SECONDS_PER_YEAR   = 31556952 # length of a gregorian year (365.2425 days) + +    PARTS_IN_SECONDS = { +      seconds: 1, +      minutes: SECONDS_PER_MINUTE, +      hours:   SECONDS_PER_HOUR, +      days:    SECONDS_PER_DAY, +      weeks:   SECONDS_PER_WEEK, +      months:  SECONDS_PER_MONTH, +      years:   SECONDS_PER_YEAR +    }.freeze      attr_accessor :value, :parts      autoload :ISO8601Parser,     "active_support/duration/iso8601_parser"      autoload :ISO8601Serializer, "active_support/duration/iso8601_serializer" +    class << self +      # Creates a new Duration from string formatted according to ISO 8601 Duration. +      # +      # See {ISO 8601}[http://en.wikipedia.org/wiki/ISO_8601#Durations] for more information. +      # This method allows negative parts to be present in pattern. +      # If invalid string is provided, it will raise +ActiveSupport::Duration::ISO8601Parser::ParsingError+. +      def parse(iso8601duration) +        parts = ISO8601Parser.new(iso8601duration).parse! +        new(calculate_total_seconds(parts), parts) +      end + +      def ===(other) #:nodoc: +        other.is_a?(Duration) +      rescue ::NoMethodError +        false +      end + +      def seconds(value) #:nodoc: +        new(value, [[:seconds, value]]) +      end + +      def minutes(value) #:nodoc: +        new(value * SECONDS_PER_MINUTE, [[:minutes, value]]) +      end + +      def hours(value) #:nodoc: +        new(value * SECONDS_PER_HOUR, [[:hours, value]]) +      end + +      def days(value) #:nodoc: +        new(value * SECONDS_PER_DAY, [[:days, value]]) +      end + +      def weeks(value) #:nodoc: +        new(value * SECONDS_PER_WEEK, [[:weeks, value]]) +      end + +      def months(value) #:nodoc: +        new(value * SECONDS_PER_MONTH, [[:months, value]]) +      end + +      def years(value) #:nodoc: +        new(value * SECONDS_PER_YEAR, [[:years, value]]) +      end + +      private + +        def calculate_total_seconds(parts) +          parts.inject(0) do |total, (part, value)| +            total + value * PARTS_IN_SECONDS[part] +          end +        end +    end +      def initialize(value, parts) #:nodoc:        @value, @parts = value, parts.to_h        @parts.default = 0 @@ -78,14 +147,14 @@ module ActiveSupport      #   1.day.to_i      # => 86400      #      # Note that this conversion makes some assumptions about the -    # duration of some periods, e.g. months are always 30 days -    # and years are 365.25 days: +    # duration of some periods, e.g. months are always 1/12 of year +    # and years are 365.2425 days:      # -    #   # equivalent to 30.days.to_i -    #   1.month.to_i    # => 2592000 +    #   # equivalent to (1.year / 12).to_i +    #   1.month.to_i    # => 2629746      # -    #   # equivalent to 365.25.days.to_i -    #   1.year.to_i     # => 31557600 +    #   # equivalent to 365.2425.days.to_i +    #   1.year.to_i     # => 31556952      #      # In such cases, Ruby's core      # Date[http://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html] and @@ -105,12 +174,6 @@ module ActiveSupport        @value.hash      end -    def self.===(other) #:nodoc: -      other.is_a?(Duration) -    rescue ::NoMethodError -      false -    end -      # Calculates a new Time or Date that is as far in the future      # as this Duration represents.      def since(time = ::Time.current) @@ -141,16 +204,6 @@ module ActiveSupport        @value.respond_to?(method, include_private)      end -    # Creates a new Duration from string formatted according to ISO 8601 Duration. -    # -    # See {ISO 8601}[http://en.wikipedia.org/wiki/ISO_8601#Durations] for more information. -    # This method allows negative parts to be present in pattern. -    # If invalid string is provided, it will raise +ActiveSupport::Duration::ISO8601Parser::ParsingError+. -    def self.parse(iso8601duration) -      parts = ISO8601Parser.new(iso8601duration).parse! -      new(EPOCH.advance(parts) - EPOCH, parts) -    end -      # Build ISO 8601 Duration string for this duration.      # The +precision+ parameter can be used to limit seconds' precision of duration.      def iso8601(precision: nil) diff --git a/activesupport/lib/active_support/evented_file_update_checker.rb b/activesupport/lib/active_support/evented_file_update_checker.rb index f54f88eb0a..8e0dc71dca 100644 --- a/activesupport/lib/active_support/evented_file_update_checker.rb +++ b/activesupport/lib/active_support/evented_file_update_checker.rb @@ -17,7 +17,7 @@ module ActiveSupport    #    # Example:    # -  #     checker = EventedFileUpdateChecker.new(["/tmp/foo"], -> { puts "changed" }) +  #     checker = ActiveSupport::EventedFileUpdateChecker.new(["/tmp/foo"]) { puts "changed" }    #     checker.updated?    #     # => false    #     checker.execute_if_updated @@ -32,6 +32,10 @@ module ActiveSupport    #    class EventedFileUpdateChecker #:nodoc: all      def initialize(files, dirs = {}, &block) +      unless block +        raise ArgumentError, "A block is required to initialize an EventedFileUpdateChecker" +      end +        @ph    = PathHelper.new        @files = files.map { |f| @ph.xpath(f) }.to_set diff --git a/activesupport/lib/active_support/execution_wrapper.rb b/activesupport/lib/active_support/execution_wrapper.rb index 3384d12d5b..ca88e7876b 100644 --- a/activesupport/lib/active_support/execution_wrapper.rb +++ b/activesupport/lib/active_support/execution_wrapper.rb @@ -19,14 +19,14 @@ module ActiveSupport        set_callback(:complete, *args, &block)      end -    class RunHook < Struct.new(:hook) # :nodoc: +    RunHook = Struct.new(:hook) do # :nodoc:        def before(target)          hook_state = target.send(:hook_state)          hook_state[hook] = hook.run        end      end -    class CompleteHook < Struct.new(:hook) # :nodoc: +    CompleteHook = Struct.new(:hook) do # :nodoc:        def before(target)          hook_state = target.send(:hook_state)          if hook_state.key?(hook) diff --git a/activesupport/lib/active_support/file_update_checker.rb b/activesupport/lib/active_support/file_update_checker.rb index 2dbbfadac1..2b5e3c1350 100644 --- a/activesupport/lib/active_support/file_update_checker.rb +++ b/activesupport/lib/active_support/file_update_checker.rb @@ -38,6 +38,10 @@ module ActiveSupport      # changes. The array of files and list of directories cannot be changed      # after FileUpdateChecker has been initialized.      def initialize(files, dirs = {}, &block) +      unless block +        raise ArgumentError, "A block is required to initialize a FileUpdateChecker" +      end +        @files = files.freeze        @glob  = compile_glob(dirs)        @block = block diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb index d3f7b46e77..79e7feaf47 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -269,6 +269,10 @@ module ActiveSupport        dup.tap { |hash| hash.transform_values!(*args, &block) }      end +    def compact +      dup.compact! +    end +      # Convert to a regular hash with string keys.      def to_hash        _new_hash = Hash.new diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb index aa68f9ec9e..c47a2e34e1 100644 --- a/activesupport/lib/active_support/inflector/inflections.rb +++ b/activesupport/lib/active_support/inflector/inflections.rb @@ -1,5 +1,6 @@  require "concurrent/map"  require "active_support/core_ext/array/prepend_and_append" +require "active_support/core_ext/regexp"  require "active_support/i18n"  module ActiveSupport @@ -43,13 +44,14 @@ module ActiveSupport          end          def add(words) -          concat(words.flatten.map(&:downcase)) -          @regex_array += map { |word| to_regex(word) } +          words = words.flatten.map(&:downcase) +          concat(words) +          @regex_array += words.map { |word| to_regex(word) }            self          end          def uncountable?(str) -          @regex_array.any? { |regex| regex === str } +          @regex_array.any? { |regex| regex.match? str }          end          private diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index 8fea96a82a..8ccb735c6d 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -198,10 +198,10 @@ module ActiveSupport      # Removes the module part from the expression in the string.      # -    #   demodulize('ActiveRecord::CoreExtensions::String::Inflections') # => "Inflections" -    #   demodulize('Inflections')                                       # => "Inflections" -    #   demodulize('::Inflections')                                     # => "Inflections" -    #   demodulize('')                                                  # => "" +    #   demodulize('ActiveSupport::Inflector::Inflections') # => "Inflections" +    #   demodulize('Inflections')                           # => "Inflections" +    #   demodulize('::Inflections')                         # => "Inflections" +    #   demodulize('')                                      # => ""      #      # See also #deconstantize.      def demodulize(path) diff --git a/activesupport/lib/active_support/inflector/transliterate.rb b/activesupport/lib/active_support/inflector/transliterate.rb index 3e78986e8e..de6dd2720b 100644 --- a/activesupport/lib/active_support/inflector/transliterate.rb +++ b/activesupport/lib/active_support/inflector/transliterate.rb @@ -57,6 +57,8 @@ module ActiveSupport      #   transliterate('Jürgen')      #   # => "Juergen"      def transliterate(string, replacement = "?".freeze) +      raise ArgumentError, "Can only transliterate strings. Received #{string.class.name}" unless string.is_a?(String) +        I18n.transliterate(ActiveSupport::Multibyte::Unicode.normalize(          ActiveSupport::Multibyte::Unicode.tidy_bytes(string), :c),            replacement: replacement) diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb index 7b33dc3481..0671469788 100644 --- a/activesupport/lib/active_support/message_encryptor.rb +++ b/activesupport/lib/active_support/message_encryptor.rb @@ -14,10 +14,10 @@ module ActiveSupport    # where you don't want users to be able to determine the value of the payload.    #    #   salt  = SecureRandom.random_bytes(64) -  #   key   = ActiveSupport::KeyGenerator.new('password').generate_key(salt) # => "\x89\xE0\x156\xAC..." -  #   crypt = ActiveSupport::MessageEncryptor.new(key)                       # => #<ActiveSupport::MessageEncryptor ...> -  #   encrypted_data = crypt.encrypt_and_sign('my secret data')              # => "NlFBTTMwOUV5UlA1QlNEN2xkY2d6eThYWWh..." -  #   crypt.decrypt_and_verify(encrypted_data)                               # => "my secret data" +  #   key   = ActiveSupport::KeyGenerator.new('password').generate_key(salt, 32) # => "\x89\xE0\x156\xAC..." +  #   crypt = ActiveSupport::MessageEncryptor.new(key)                           # => #<ActiveSupport::MessageEncryptor ...> +  #   encrypted_data = crypt.encrypt_and_sign('my secret data')                  # => "NlFBTTMwOUV5UlA1QlNEN2xkY2d6eThYWWh..." +  #   crypt.decrypt_and_verify(encrypted_data)                                   # => "my secret data"    class MessageEncryptor      DEFAULT_CIPHER = "aes-256-cbc" diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb index 05cfb249c3..0912912aba 100644 --- a/activesupport/lib/active_support/multibyte/unicode.rb +++ b/activesupport/lib/active_support/multibyte/unicode.rb @@ -9,7 +9,7 @@ module ActiveSupport        NORMALIZATION_FORMS = [:c, :kc, :d, :kd]        # The Unicode version that is supported by the implementation -      UNICODE_VERSION = "8.0.0" +      UNICODE_VERSION = "9.0.0"        # The default normalization used for operations that require        # normalization. It can be set to any of the normalizations @@ -57,9 +57,12 @@ module ActiveSupport            previous = codepoints[pos - 1]            current = codepoints[pos] +          # See http://unicode.org/reports/tr29/#Grapheme_Cluster_Boundary_Rules            should_break = +            if pos == eoc +              true              # GB3. CR X LF -            if previous == database.boundary[:cr] && current == database.boundary[:lf] +            elsif previous == database.boundary[:cr] && current == database.boundary[:lf]                false              # GB4. (Control|CR|LF) ÷              elsif previous && in_char_class?(previous, [:control, :cr, :lf]) @@ -76,11 +79,8 @@ module ActiveSupport              # GB8. (LVT|T) X (T)              elsif in_char_class?(previous, [:lvt, :t]) && database.boundary[:t] === current                false -            # GB8a. Regional_Indicator X Regional_Indicator -            elsif database.boundary[:regional_indicator] === previous && database.boundary[:regional_indicator] === current -              false -            # GB9. X Extend -            elsif database.boundary[:extend] === current +            # GB9. X (Extend | ZWJ) +            elsif in_char_class?(current, [:extend, :zwj])                false              # GB9a. X SpacingMark              elsif database.boundary[:spacingmark] === current @@ -88,7 +88,17 @@ module ActiveSupport              # GB9b. Prepend X              elsif database.boundary[:prepend] === previous                false -            # GB10. Any ÷ Any +            # GB10. (E_Base | EBG) Extend* X E_Modifier +            elsif (marker...pos).any? { |i| in_char_class?(codepoints[i], [:e_base, :e_base_gaz]) && codepoints[i + 1...pos].all? { |c| database.boundary[:extend] === c } } && database.boundary[:e_modifier] === current +              false +            # GB11. ZWJ X (Glue_After_Zwj | EBG) +            elsif database.boundary[:zwj] === previous && in_char_class?(current, [:glue_after_zwj, :e_base_gaz]) +              false +            # GB12. ^ (RI RI)* RI X RI +            # GB13. [^RI] (RI RI)* RI X RI +            elsif codepoints[marker..pos].all? { |c| database.boundary[:regional_indicator] === c } && codepoints[marker..pos].count { |c| database.boundary[:regional_indicator] === c }.even? +              false +            # GB999. Any ÷ Any              else                true              end diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb index 6000ea44be..880340ca86 100644 --- a/activesupport/lib/active_support/number_helper.rb +++ b/activesupport/lib/active_support/number_helper.rb @@ -45,7 +45,7 @@ module ActiveSupport      #      #   number_to_phone(75561234567, pattern: /(\d{1,4})(\d{4})(\d{4})$/, area_code: true)      #   # => "(755) 6123-4567" -    #   number_to_phone(13312345678, pattern: /(\d{3})(\d{4})(\d{4})$/)) +    #   number_to_phone(13312345678, pattern: /(\d{3})(\d{4})(\d{4})$/)      #   # => "133-1234-5678"      def number_to_phone(number, options = {})        NumberToPhoneConverter.convert(number, options) @@ -78,7 +78,7 @@ module ActiveSupport      #   (defaults to "%u%n").  Fields are <tt>%u</tt> for the      #   currency, and <tt>%n</tt> for the number.      # * <tt>:negative_format</tt> - Sets the format for negative -    #   numbers (defaults to prepending an hyphen to the formatted +    #   numbers (defaults to prepending a hyphen to the formatted      #   number given by <tt>:format</tt>).  Accepts the same fields      #   than <tt>:format</tt>, except <tt>%n</tt> is here the      #   absolute value of the number. diff --git a/activesupport/lib/active_support/string_inquirer.rb b/activesupport/lib/active_support/string_inquirer.rb index 09e1cbb28d..90eac89c9e 100644 --- a/activesupport/lib/active_support/string_inquirer.rb +++ b/activesupport/lib/active_support/string_inquirer.rb @@ -18,7 +18,7 @@ module ActiveSupport      private        def respond_to_missing?(method_name, include_private = false) -        method_name[-1] == "?" +        (method_name[-1] == "?") || super        end        def method_missing(method_name, *arguments) diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb index 14edd13a8f..54c3263efa 100644 --- a/activesupport/lib/active_support/testing/isolation.rb +++ b/activesupport/lib/active_support/testing/isolation.rb @@ -43,7 +43,7 @@ module ActiveSupport                    end                  }                end -              result = Marshal.dump(self.dup) +              result = Marshal.dump(dup)              end              write.puts [result].pack("m") @@ -78,7 +78,7 @@ module ActiveSupport                  "ISOLATION_OUTPUT" => tmpfile.path                } -              test_opts = "-n#{self.class.name}##{self.name}" +              test_opts = "-n#{self.class.name}##{name}"                load_path_args = []                $-I.each do |p| diff --git a/activesupport/lib/active_support/testing/time_helpers.rb b/activesupport/lib/active_support/testing/time_helpers.rb index e2f008b4b7..07c9be0604 100644 --- a/activesupport/lib/active_support/testing/time_helpers.rb +++ b/activesupport/lib/active_support/testing/time_helpers.rb @@ -10,7 +10,7 @@ module ActiveSupport          @stubs = Concurrent::Map.new { |h, k| h[k] = {} }        end -      def stub_object(object, method_name, return_value) +      def stub_object(object, method_name, &block)          if stub = stubbing(object, method_name)            unstub_object(stub)          end @@ -20,7 +20,7 @@ module ActiveSupport          @stubs[object.object_id][method_name] = Stub.new(object, method_name, new_name)          object.singleton_class.send :alias_method, new_name, method_name -        object.define_singleton_method(method_name) { return_value } +        object.define_singleton_method(method_name, &block)        end        def unstub_all! @@ -134,9 +134,9 @@ module ActiveSupport            now = date_or_time.to_time.change(usec: 0)          end -        simple_stubs.stub_object(Time, :now, now) -        simple_stubs.stub_object(Date, :today, now.to_date) -        simple_stubs.stub_object(DateTime, :now, now.to_datetime) +        simple_stubs.stub_object(Time, :now) { at(now.to_i) } +        simple_stubs.stub_object(Date, :today) { jd(now.to_date.jd) } +        simple_stubs.stub_object(DateTime, :now) { jd(now.to_date.jd, now.hour, now.min, now.sec, Rational(now.utc_offset, 86400)) }          if block_given?            begin diff --git a/activesupport/lib/active_support/values/unicode_tables.dat b/activesupport/lib/active_support/values/unicode_tables.dat Binary files differindex dd2c178fb6..f7d9c48bbe 100644 --- a/activesupport/lib/active_support/values/unicode_tables.dat +++ b/activesupport/lib/active_support/values/unicode_tables.dat diff --git a/activesupport/lib/active_support/xml_mini.rb b/activesupport/lib/active_support/xml_mini.rb index ac6db6618d..782fb41288 100644 --- a/activesupport/lib/active_support/xml_mini.rb +++ b/activesupport/lib/active_support/xml_mini.rb @@ -73,7 +73,7 @@ module ActiveSupport              begin                BigDecimal(number)              rescue ArgumentError -              BigDecimal('0') +              BigDecimal("0")              end            else              BigDecimal(number) diff --git a/activesupport/lib/active_support/xml_mini/libxmlsax.rb b/activesupport/lib/active_support/xml_mini/libxmlsax.rb index 4edb0bd2b1..8a43b05b17 100644 --- a/activesupport/lib/active_support/xml_mini/libxmlsax.rb +++ b/activesupport/lib/active_support/xml_mini/libxmlsax.rb @@ -73,7 +73,7 @@ module ActiveSupport          LibXML::XML::Error.set_handler(&LibXML::XML::Error::QUIET_HANDLER)          parser = LibXML::XML::SaxParser.io(data) -        document = self.document_class.new +        document = document_class.new          parser.callbacks = document          parser.parse diff --git a/activesupport/lib/active_support/xml_mini/nokogirisax.rb b/activesupport/lib/active_support/xml_mini/nokogirisax.rb index b8b85146c5..7388bea5d8 100644 --- a/activesupport/lib/active_support/xml_mini/nokogirisax.rb +++ b/activesupport/lib/active_support/xml_mini/nokogirisax.rb @@ -76,7 +76,7 @@ module ActiveSupport          {}        else          data.ungetc(char) -        document = self.document_class.new +        document = document_class.new          parser = Nokogiri::XML::SAX::Parser.new(document)          parser.parse(data)          document.hash diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb index 4e564591b4..c4f34c0abf 100644 --- a/activesupport/test/abstract_unit.rb +++ b/activesupport/test/abstract_unit.rb @@ -3,8 +3,8 @@ ORIG_ARGV = ARGV.dup  require "active_support/core_ext/kernel/reporting"  silence_warnings do -  Encoding.default_internal = "UTF-8" -  Encoding.default_external = "UTF-8" +  Encoding.default_internal = Encoding::UTF_8 +  Encoding.default_external = Encoding::UTF_8  end  require "active_support/testing/autorun" @@ -24,16 +24,16 @@ ActiveSupport.to_time_preserves_timezone = ENV["PRESERVE_TIMEZONES"] == "1"  # Disable available locale checks to avoid warnings running the test suite.  I18n.enforce_available_locales = false -# Skips the current run on Rubinius using Minitest::Assertions#skip -def rubinius_skip(message = "") -  skip message if RUBY_ENGINE == "rbx" -end - -# Skips the current run on JRuby using Minitest::Assertions#skip -def jruby_skip(message = "") -  skip message if defined?(JRUBY_VERSION) -end -  class ActiveSupport::TestCase    include ActiveSupport::Testing::MethodCallAssertions + +  # Skips the current run on Rubinius using Minitest::Assertions#skip +  private def rubinius_skip(message = "") +    skip message if RUBY_ENGINE == "rbx" +  end + +  # Skips the current run on JRuby using Minitest::Assertions#skip +  private def jruby_skip(message = "") +    skip message if defined?(JRUBY_VERSION) +  end  end diff --git a/activesupport/test/array_inquirer_test.rb b/activesupport/test/array_inquirer_test.rb index 4d3f5b001c..5b2bc82905 100644 --- a/activesupport/test/array_inquirer_test.rb +++ b/activesupport/test/array_inquirer_test.rb @@ -38,4 +38,24 @@ class ArrayInquirerTest < ActiveSupport::TestCase      assert_instance_of ActiveSupport::ArrayInquirer, result      assert_equal @array_inquirer, result    end + +  def test_respond_to_fallback_to_array_respond_to +    Array.class_eval do +      def respond_to_missing?(name, include_private = false) +        (name == :foo) || super +      end +    end +    arr = ActiveSupport::ArrayInquirer.new([:x]) + +    assert_respond_to arr, :can_you_hear_me? +    assert_respond_to arr, :foo +    assert_not_respond_to arr, :nope +  ensure +    Array.class_eval do +      undef_method :respond_to_missing? +      def respond_to_missing?(name, include_private = false) +        super +      end +    end +  end  end diff --git a/activesupport/test/autoloading_fixtures/raises_arbitrary_exception.rb b/activesupport/test/autoloading_fixtures/raises_arbitrary_exception.rb index 829ee917d2..e477ab21d0 100644 --- a/activesupport/test/autoloading_fixtures/raises_arbitrary_exception.rb +++ b/activesupport/test/autoloading_fixtures/raises_arbitrary_exception.rb @@ -1,4 +1,4 @@  RaisesArbitraryException = 1  _ = A::B # Autoloading recursion, also expected to be watched and discarded. -raise Exception, "arbitray exception message" +raise Exception, "arbitrary exception message" diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb index b3bed19dba..28caa30bf1 100644 --- a/activesupport/test/callbacks_test.rb +++ b/activesupport/test/callbacks_test.rb @@ -1040,7 +1040,7 @@ module CallbacksTest          set_callback :foo, :before, :foo, if: callback          def run; run_callbacks :foo; end          private -        def foo; end +          def foo; end        }        object = klass.new        object.run diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb index 5a90210bb8..50bb1004f7 100644 --- a/activesupport/test/core_ext/date_ext_test.rb +++ b/activesupport/test/core_ext/date_ext_test.rb @@ -395,6 +395,10 @@ class DateExtBehaviorTest < ActiveSupport::TestCase      assert Date.new.acts_like_date?    end +  def test_blank? +    assert_not Date.new.blank? +  end +    def test_freeze_doesnt_clobber_memoized_instance_methods      assert_nothing_raised do        Date.today.freeze.inspect diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb index e9be181749..e3b31c10f5 100644 --- a/activesupport/test/core_ext/date_time_ext_test.rb +++ b/activesupport/test/core_ext/date_time_ext_test.rb @@ -318,6 +318,10 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase      assert DateTime.new.acts_like_time?    end +  def test_blank? +    assert_not DateTime.new.blank? +  end +    def test_utc?      assert_equal true, DateTime.civil(2005, 2, 21, 10, 11, 12).utc?      assert_equal true, DateTime.civil(2005, 2, 21, 10, 11, 12, 0).utc? diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb index 6f7f16da5b..6a275d1d5b 100644 --- a/activesupport/test/core_ext/duration_test.rb +++ b/activesupport/test/core_ext/duration_test.rb @@ -196,7 +196,7 @@ class DurationTest < ActiveSupport::TestCase      assert_nothing_raised do        1.minute.times { counter += 1 }      end -    assert_equal counter, 60 +    assert_equal 60, counter    end    def test_as_json @@ -213,7 +213,7 @@ class DurationTest < ActiveSupport::TestCase        when 1.day          "ok"        end -    assert_equal cased, "ok" +    assert_equal "ok", cased    end    def test_respond_to @@ -237,6 +237,29 @@ class DurationTest < ActiveSupport::TestCase      assert_equal(1, (61 <=> 1.minute))    end +  def test_twelve_months_equals_one_year +    assert_equal 12.months, 1.year +  end + +  def test_thirty_days_does_not_equal_one_month +    assert_not_equal 30.days, 1.month +  end + +  def test_adding_one_month_maintains_day_of_month +    (1..11).each do |month| +      [1, 14, 28].each do |day| +        assert_equal Date.civil(2016, month + 1, day), Date.civil(2016, month, day) + 1.month +      end +    end + +    assert_equal Date.civil(2017, 1, 1),  Date.civil(2016, 12, 1) + 1.month +    assert_equal Date.civil(2017, 1, 14),  Date.civil(2016, 12, 14) + 1.month +    assert_equal Date.civil(2017, 1, 28),  Date.civil(2016, 12, 28) + 1.month + +    assert_equal Date.civil(2015, 2, 28), Date.civil(2015, 1, 31) + 1.month +    assert_equal Date.civil(2016, 2, 29), Date.civil(2016, 1, 31) + 1.month +  end +    # ISO8601 string examples are taken from ISO8601 gem at https://github.com/arnau/ISO8601/blob/b93d466840/spec/iso8601/duration_spec.rb    # published under the conditions of MIT license at https://github.com/arnau/ISO8601/blob/b93d466840/LICENSE    # @@ -345,6 +368,21 @@ class DurationTest < ActiveSupport::TestCase      end    end +  def test_iso8601_parsing_equivalence_with_numeric_extensions_over_long_periods +    with_env_tz eastern_time_zone do +      with_tz_default "Eastern Time (US & Canada)" do +        assert_equal 3.months, ActiveSupport::Duration.parse("P3M") +        assert_equal 3.months.to_i, ActiveSupport::Duration.parse("P3M").to_i +        assert_equal 10.months, ActiveSupport::Duration.parse("P10M") +        assert_equal 10.months.to_i, ActiveSupport::Duration.parse("P10M").to_i +        assert_equal 3.years, ActiveSupport::Duration.parse("P3Y") +        assert_equal 3.years.to_i, ActiveSupport::Duration.parse("P3Y").to_i +        assert_equal 10.years, ActiveSupport::Duration.parse("P10Y") +        assert_equal 10.years.to_i, ActiveSupport::Duration.parse("P10Y").to_i +      end +    end +  end +    def test_adding_durations_do_not_hold_prior_states      time = Time.parse("Nov 29, 2016")      # If the implementation adds and subtracts 3 months, the diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index ff12632f84..05813ad388 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -111,7 +111,7 @@ class HashExtTest < ActiveSupport::TestCase      transformed_hash = @mixed.dup      transformed_hash.transform_keys! { |key| key.to_s.upcase }      assert_equal @upcase_strings, transformed_hash -    assert_equal @mixed, :a => 1, "b" => 2 +    assert_equal({ :a => 1, "b" => 2 }, @mixed)    end    def test_deep_transform_keys! @@ -127,7 +127,7 @@ class HashExtTest < ActiveSupport::TestCase      transformed_hash = @nested_mixed.deep_dup      transformed_hash.deep_transform_keys! { |key| key.to_s.upcase }      assert_equal @nested_upcase_strings, transformed_hash -    assert_equal @nested_mixed, "a" => { b: { "c" => 3 } } +    assert_equal({ "a" => { b: { "c" => 3 } } }, @nested_mixed)    end    def test_symbolize_keys @@ -167,7 +167,7 @@ class HashExtTest < ActiveSupport::TestCase      transformed_hash = @mixed.dup      transformed_hash.deep_symbolize_keys!      assert_equal @symbols, transformed_hash -    assert_equal @mixed, :a => 1, "b" => 2 +    assert_equal({ :a => 1, "b" => 2 }, @mixed)    end    def test_deep_symbolize_keys! @@ -183,7 +183,7 @@ class HashExtTest < ActiveSupport::TestCase      transformed_hash = @nested_mixed.deep_dup      transformed_hash.deep_symbolize_keys!      assert_equal @nested_symbols, transformed_hash -    assert_equal @nested_mixed, "a" => { b: { "c" => 3 } } +    assert_equal({ "a" => { b: { "c" => 3 } } }, @nested_mixed)    end    def test_symbolize_keys_preserves_keys_that_cant_be_symbolized @@ -243,7 +243,7 @@ class HashExtTest < ActiveSupport::TestCase      transformed_hash = @mixed.dup      transformed_hash.stringify_keys!      assert_equal @strings, transformed_hash -    assert_equal @mixed, :a => 1, "b" => 2 +    assert_equal({ :a => 1, "b" => 2 }, @mixed)    end    def test_deep_stringify_keys! @@ -259,7 +259,7 @@ class HashExtTest < ActiveSupport::TestCase      transformed_hash = @nested_mixed.deep_dup      transformed_hash.deep_stringify_keys!      assert_equal @nested_strings, transformed_hash -    assert_equal @nested_mixed, "a" => { b: { "c" => 3 } } +    assert_equal({ "a" => { b: { "c" => 3 } } }, @nested_mixed)    end    def test_symbolize_keys_for_hash_with_indifferent_access @@ -414,11 +414,11 @@ class HashExtTest < ActiveSupport::TestCase      hash["b"] = 2      hash[3] = 3 -    assert_equal hash["a"], 1 -    assert_equal hash["b"], 2 -    assert_equal hash[:a], 1 -    assert_equal hash[:b], 2 -    assert_equal hash[3], 3 +    assert_equal 1, hash["a"] +    assert_equal 2, hash["b"] +    assert_equal 1, hash[:a] +    assert_equal 2, hash[:b] +    assert_equal 3, hash[3]    end    def test_indifferent_update @@ -430,16 +430,16 @@ class HashExtTest < ActiveSupport::TestCase      updated_with_symbols = hash.update(@symbols)      updated_with_mixed = hash.update(@mixed) -    assert_equal updated_with_strings[:a], 1 -    assert_equal updated_with_strings["a"], 1 -    assert_equal updated_with_strings["b"], 2 +    assert_equal 1, updated_with_strings[:a] +    assert_equal 1, updated_with_strings["a"] +    assert_equal 2, updated_with_strings["b"] -    assert_equal updated_with_symbols[:a], 1 -    assert_equal updated_with_symbols["b"], 2 -    assert_equal updated_with_symbols[:b], 2 +    assert_equal 1, updated_with_symbols[:a] +    assert_equal 2, updated_with_symbols["b"] +    assert_equal 2, updated_with_symbols[:b] -    assert_equal updated_with_mixed[:a], 1 -    assert_equal updated_with_mixed["b"], 2 +    assert_equal 1, updated_with_mixed[:a] +    assert_equal 2, updated_with_mixed["b"]      assert [updated_with_strings, updated_with_symbols, updated_with_mixed].all? { |h| h.keys.size == 2 }    end @@ -447,7 +447,7 @@ class HashExtTest < ActiveSupport::TestCase    def test_update_with_to_hash_conversion      hash = HashWithIndifferentAccess.new      hash.update HashByConversion.new(a: 1) -    assert_equal hash["a"], 1 +    assert_equal 1, hash["a"]    end    def test_indifferent_merging @@ -472,7 +472,7 @@ class HashExtTest < ActiveSupport::TestCase    def test_merge_with_to_hash_conversion      hash = HashWithIndifferentAccess.new      merged = hash.merge HashByConversion.new(a: 1) -    assert_equal merged["a"], 1 +    assert_equal 1, merged["a"]    end    def test_indifferent_replace @@ -536,11 +536,11 @@ class HashExtTest < ActiveSupport::TestCase    def test_indifferent_deleting      get_hash = proc { { a: "foo" }.with_indifferent_access }      hash = get_hash.call -    assert_equal hash.delete(:a), "foo" -    assert_equal hash.delete(:a), nil +    assert_equal "foo", hash.delete(:a) +    assert_nil hash.delete(:a)      hash = get_hash.call -    assert_equal hash.delete("a"), "foo" -    assert_equal hash.delete("a"), nil +    assert_equal "foo", hash.delete("a") +    assert_nil hash.delete("a")    end    def test_indifferent_select @@ -589,6 +589,16 @@ class HashExtTest < ActiveSupport::TestCase      assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings    end +  def test_indifferent_compact +    hash_contain_nil_value = @strings.merge("z" => nil) +    hash = ActiveSupport::HashWithIndifferentAccess.new(hash_contain_nil_value) +    compacted_hash = hash.compact + +    assert_equal(@strings, compacted_hash) +    assert_equal(hash_contain_nil_value, hash) +    assert_instance_of ActiveSupport::HashWithIndifferentAccess, compacted_hash +  end +    def test_indifferent_to_hash      # Should convert to a Hash with String keys.      assert_equal @strings, @mixed.with_indifferent_access.to_hash @@ -1017,7 +1027,7 @@ class HashExtTest < ActiveSupport::TestCase      assert_equal({}, h)      h = @symbols.dup -    assert_equal(nil, h.compact!) +    assert_nil(h.compact!)      assert_equal(@symbols, h)    end diff --git a/activesupport/test/core_ext/kernel_test.rb b/activesupport/test/core_ext/kernel_test.rb index db0008b735..26f5088ede 100644 --- a/activesupport/test/core_ext/kernel_test.rb +++ b/activesupport/test/core_ext/kernel_test.rb @@ -49,19 +49,3 @@ class KernelSuppressTest < ActiveSupport::TestCase      suppress(LoadError, ArgumentError) { raise ArgumentError }    end  end - -class MockStdErr -  attr_reader :output -  def puts(message) -    @output ||= [] -    @output << message -  end - -  def info(message) -    puts(message) -  end - -  def write(message) -    puts(message) -  end -end diff --git a/activesupport/test/core_ext/module/attribute_accessor_per_thread_test.rb b/activesupport/test/core_ext/module/attribute_accessor_per_thread_test.rb index b816fa50e3..af240bc38d 100644 --- a/activesupport/test/core_ext/module/attribute_accessor_per_thread_test.rb +++ b/activesupport/test/core_ext/module/attribute_accessor_per_thread_test.rb @@ -121,11 +121,11 @@ class ModuleAttributeAccessorPerThreadTest < ActiveSupport::TestCase    def test_should_not_affect_superclass_if_subclass_set_value      @class.foo = "super" -    assert_equal @class.foo, "super" +    assert_equal "super", @class.foo      assert_nil @subclass.foo      @subclass.foo = "sub" -    assert_equal @class.foo, "super" -    assert_equal @subclass.foo, "sub" +    assert_equal "super", @class.foo +    assert_equal "sub", @subclass.foo    end  end diff --git a/activesupport/test/core_ext/module/attribute_aliasing_test.rb b/activesupport/test/core_ext/module/attribute_aliasing_test.rb index d8c2dfd6b8..fdfa868851 100644 --- a/activesupport/test/core_ext/module/attribute_aliasing_test.rb +++ b/activesupport/test/core_ext/module/attribute_aliasing_test.rb @@ -52,8 +52,8 @@ class AttributeAliasingTest < ActiveSupport::TestCase      assert_equal "No, really, this is not a joke.", e.Data      assert e.Data? -    e.Data = "Uppercased methods are teh suck" -    assert_equal "Uppercased methods are teh suck", e.body +    e.Data = "Uppercased methods are the suck" +    assert_equal "Uppercased methods are the suck", e.body      assert e.body?    end  end diff --git a/activesupport/test/core_ext/module/introspection_test.rb b/activesupport/test/core_ext/module/introspection_test.rb new file mode 100644 index 0000000000..db383850cd --- /dev/null +++ b/activesupport/test/core_ext/module/introspection_test.rb @@ -0,0 +1,37 @@ +require "abstract_unit" +require "active_support/core_ext/module/introspection" + +module ParentA +  module B +    module C; end +    module FrozenC; end +    FrozenC.freeze +  end + +  module FrozenB; end +  FrozenB.freeze +end + +class IntrospectionTest < ActiveSupport::TestCase +  def test_parent_name +    assert_equal "ParentA", ParentA::B.parent_name +    assert_equal "ParentA::B", ParentA::B::C.parent_name +    assert_nil ParentA.parent_name +  end + +  def test_parent_name_when_frozen +    assert_equal "ParentA", ParentA::FrozenB.parent_name +    assert_equal "ParentA::B", ParentA::B::FrozenC.parent_name +  end + +  def test_parent +    assert_equal ParentA::B, ParentA::B::C.parent +    assert_equal ParentA, ParentA::B.parent +    assert_equal Object, ParentA.parent +  end + +  def test_parents +    assert_equal [ParentA::B, ParentA, Object], ParentA::B::C.parents +    assert_equal [ParentA, Object], ParentA::B.parents +  end +end diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb index 8f2df22f27..a17438bf4d 100644 --- a/activesupport/test/core_ext/module_test.rb +++ b/activesupport/test/core_ext/module_test.rb @@ -1,30 +1,11 @@  require "abstract_unit"  require "active_support/core_ext/module" -module One -  Constant1 = "Hello World" -  Constant2 = "What's up?" -end - -class Ab -  include One -  Constant1 = "Hello World" # Will have different object id than One::Constant1 -  Constant3 = "Goodbye World" -end - -module Yz -  module Zy -    class Cd -      include One -    end -  end -end -  Somewhere = Struct.new(:street, :city) do    attr_accessor :name  end -class Someone < Struct.new(:name, :place) +Someone = Struct.new(:name, :place) do    delegate :street, :city, :to_f, to: :place    delegate :name=, to: :place, prefix: true    delegate :upcase, to: "place.city" @@ -35,10 +16,10 @@ class Someone < Struct.new(:name, :place)      "some_table"    end -  FAILED_DELEGATE_LINE = __LINE__ + 1 +  self::FAILED_DELEGATE_LINE = __LINE__ + 1    delegate :foo, to: :place -  FAILED_DELEGATE_LINE_2 = __LINE__ + 1 +  self::FAILED_DELEGATE_LINE_2 = __LINE__ + 1    delegate :bar, to: :place, allow_nil: true    private @@ -210,21 +191,21 @@ class ModuleTest < ActiveSupport::TestCase    def test_delegation_prefix      invoice = Invoice.new(@david) -    assert_equal invoice.client_name, "David" -    assert_equal invoice.client_street, "Paulina" -    assert_equal invoice.client_city, "Chicago" +    assert_equal "David", invoice.client_name +    assert_equal "Paulina", invoice.client_street +    assert_equal "Chicago", invoice.client_city    end    def test_delegation_custom_prefix      invoice = Invoice.new(@david) -    assert_equal invoice.customer_name, "David" -    assert_equal invoice.customer_street, "Paulina" -    assert_equal invoice.customer_city, "Chicago" +    assert_equal "David", invoice.customer_name +    assert_equal "Paulina", invoice.customer_street +    assert_equal "Chicago", invoice.customer_city    end    def test_delegation_prefix_with_nil_or_false -    assert_equal Developer.new(@david).name, "David" -    assert_equal Tester.new(@david).name, "David" +    assert_equal "David", Developer.new(@david).name +    assert_equal "David", Tester.new(@david).name    end    def test_delegation_prefix_with_instance_variable @@ -240,7 +221,7 @@ class ModuleTest < ActiveSupport::TestCase    def test_delegation_with_allow_nil      rails = Project.new("Rails", Someone.new("David")) -    assert_equal rails.name, "David" +    assert_equal "David", rails.name    end    def test_delegation_with_allow_nil_and_nil_value @@ -375,17 +356,6 @@ class ModuleTest < ActiveSupport::TestCase      assert_match(/undefined method `my_fake_method' for/, e.message)    end -  def test_parent -    assert_equal Yz::Zy, Yz::Zy::Cd.parent -    assert_equal Yz, Yz::Zy.parent -    assert_equal Object, Yz.parent -  end - -  def test_parents -    assert_equal [Yz::Zy, Yz, Object], Yz::Zy::Cd.parents -    assert_equal [Yz, Object], Yz::Zy.parents -  end -    def test_delegate_with_case      event = Event.new(Tester.new)      assert_equal 1, event.foo diff --git a/activesupport/test/core_ext/numeric_ext_test.rb b/activesupport/test/core_ext/numeric_ext_test.rb index 5361b7b708..3cfbe6e7e6 100644 --- a/activesupport/test/core_ext/numeric_ext_test.rb +++ b/activesupport/test/core_ext/numeric_ext_test.rb @@ -12,7 +12,7 @@ class NumericExtTimeAndDateTimeTest < ActiveSupport::TestCase        10.minutes => 600,        1.hour + 15.minutes => 4500,        2.days + 4.hours + 30.minutes => 189000, -      5.years + 1.month + 1.fortnight => 161589600 +      5.years + 1.month + 1.fortnight => 161624106      }    end @@ -61,10 +61,10 @@ class NumericExtTimeAndDateTimeTest < ActiveSupport::TestCase    end    def test_duration_after_conversion_is_no_longer_accurate -    assert_equal 30.days.to_i.seconds.since(@now), 1.month.to_i.seconds.since(@now) -    assert_equal 365.25.days.to_f.seconds.since(@now), 1.year.to_f.seconds.since(@now) -    assert_equal 30.days.to_i.seconds.since(@dtnow), 1.month.to_i.seconds.since(@dtnow) -    assert_equal 365.25.days.to_f.seconds.since(@dtnow), 1.year.to_f.seconds.since(@dtnow) +    assert_equal (1.year / 12).to_i.seconds.since(@now), 1.month.to_i.seconds.since(@now) +    assert_equal 365.2425.days.to_f.seconds.since(@now), 1.year.to_f.seconds.since(@now) +    assert_equal (1.year / 12).to_i.seconds.since(@dtnow), 1.month.to_i.seconds.since(@dtnow) +    assert_equal 365.2425.days.to_f.seconds.since(@dtnow), 1.year.to_f.seconds.since(@dtnow)    end    def test_add_one_year_to_leap_day @@ -394,6 +394,10 @@ class NumericExtFormattingTest < ActiveSupport::TestCase      assert_equal "1000010.0", BigDecimal("1000010").to_s      assert_equal "10000 10.0", BigDecimal("1000010").to_s("5F") + +    assert_raises TypeError do +      1.to_s({}) +    end    end    def test_in_milliseconds diff --git a/activesupport/test/core_ext/object/duplicable_test.rb b/activesupport/test/core_ext/object/duplicable_test.rb index f78a5f8496..e6f3c8aef2 100644 --- a/activesupport/test/core_ext/object/duplicable_test.rb +++ b/activesupport/test/core_ext/object/duplicable_test.rb @@ -4,9 +4,12 @@ require "active_support/core_ext/object/duplicable"  require "active_support/core_ext/numeric/time"  class DuplicableTest < ActiveSupport::TestCase -  if RUBY_VERSION >= "2.4.0" +  if RUBY_VERSION >= "2.4.1"      RAISE_DUP = [method(:puts), Complex(1), Rational(1)] -    ALLOW_DUP = ["1", Object.new, /foo/, [], {}, Time.now, Class.new, Module.new, BigDecimal.new("4.56"), nil, false, true, :symbol, 1, 2.3] +    ALLOW_DUP = ["1", "symbol_from_string".to_sym, Object.new, /foo/, [], {}, Time.now, Class.new, Module.new, BigDecimal.new("4.56"), nil, false, true, 1, 2.3] +  elsif RUBY_VERSION >= "2.4.0"  # Due to 2.4.0 bug. This elsif cannot be removed unless we drop 2.4.0 support... +    RAISE_DUP = [method(:puts), Complex(1), Rational(1), "symbol_from_string".to_sym] +    ALLOW_DUP = ["1", Object.new, /foo/, [], {}, Time.now, Class.new, Module.new, BigDecimal.new("4.56"), nil, false, true, 1, 2.3]    else      RAISE_DUP = [nil, false, true, :symbol, 1, 2.3, method(:puts), Complex(1), Rational(1)]      ALLOW_DUP = ["1", Object.new, /foo/, [], {}, Time.now, Class.new, Module.new, BigDecimal.new("4.56")] @@ -17,7 +20,7 @@ class DuplicableTest < ActiveSupport::TestCase                    "* https://github.com/rubinius/rubinius/issues/3089"      RAISE_DUP.each do |v| -      assert !v.duplicable? +      assert !v.duplicable?, "#{ v.inspect } should not be duplicable"        assert_raises(TypeError, v.class.name) { v.dup }      end diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index 7ae83cb964..5d90fa2509 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -152,37 +152,37 @@ class StringInflectionsTest < ActiveSupport::TestCase    def test_string_parameterized_normal      StringToParameterized.each do |normal, slugged| -      assert_equal(normal.parameterize, slugged) +      assert_equal(slugged, normal.parameterize)      end    end    def test_string_parameterized_normal_preserve_case      StringToParameterizedPreserveCase.each do |normal, slugged| -      assert_equal(normal.parameterize(preserve_case: true), slugged) +      assert_equal(slugged, normal.parameterize(preserve_case: true))      end    end    def test_string_parameterized_no_separator      StringToParameterizeWithNoSeparator.each do |normal, slugged| -      assert_equal(normal.parameterize(separator: ""), slugged) +      assert_equal(slugged, normal.parameterize(separator: ""))      end    end    def test_string_parameterized_no_separator_preserve_case      StringToParameterizePreserveCaseWithNoSeparator.each do |normal, slugged| -      assert_equal(normal.parameterize(separator: "", preserve_case: true), slugged) +      assert_equal(slugged, normal.parameterize(separator: "", preserve_case: true))      end    end    def test_string_parameterized_underscore      StringToParameterizeWithUnderscore.each do |normal, slugged| -      assert_equal(normal.parameterize(separator: "_"), slugged) +      assert_equal(slugged, normal.parameterize(separator: "_"))      end    end    def test_string_parameterized_underscore_preserve_case      StringToParameterizePreserceCaseWithUnderscore.each do |normal, slugged| -      assert_equal(normal.parameterize(separator: "_", preserve_case: true), slugged) +      assert_equal(slugged, normal.parameterize(separator: "_", preserve_case: true))      end    end @@ -222,7 +222,7 @@ class StringInflectionsTest < ActiveSupport::TestCase      original = %{\u205f\u3000 A string surrounded by various unicode spaces,        with tabs(\t\t), newlines(\n\n), unicode nextlines(\u0085\u0085) and many spaces(  ). \u00a0\u2007} -    expected = "A string surrounded by various unicode spaces, " + +    expected = "A string surrounded by various unicode spaces, " \        "with tabs( ), newlines( ), unicode nextlines( ) and many spaces( )."      # Make sure squish returns what we expect: @@ -283,7 +283,7 @@ class StringInflectionsTest < ActiveSupport::TestCase    def test_truncate_words_with_complex_string      Timeout.timeout(10) do        complex_string = "aa aa aaa aa aaa aaa aaa aa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaaa aaaaa aaaaa aaaaaa aa aa aa aaa aa  aaa aa aa aa aa a aaa aaa \n a aaa <<s" -      assert_equal complex_string.truncate_words(80), complex_string +      assert_equal complex_string, complex_string.truncate_words(80)      end    rescue Timeout::Error      assert false @@ -710,14 +710,14 @@ class OutputSafetyTest < ActiveSupport::TestCase    test "Prepending safe onto unsafe yields unsafe" do      @string.prepend "other".html_safe      assert !@string.html_safe? -    assert_equal @string, "otherhello" +    assert_equal "otherhello", @string    end    test "Prepending unsafe onto safe yields escaped safe" do      other = "other".html_safe      other.prepend "<foo>"      assert other.html_safe? -    assert_equal other, "<foo>other" +    assert_equal "<foo>other", other    end    test "Concatting safe onto unsafe yields unsafe" do diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb index 629666947f..ab5ec98642 100644 --- a/activesupport/test/core_ext/time_with_zone_test.rb +++ b/activesupport/test/core_ext/time_with_zone_test.rb @@ -455,6 +455,10 @@ class TimeWithZoneTest < ActiveSupport::TestCase      assert_equal false, ActiveSupport::TimeWithZone.new(DateTime.civil(2000), @time_zone).acts_like?(:date)    end +  def test_blank? +    assert_not @twz.blank? +  end +    def test_is_a      assert_kind_of Time, @twz      assert_kind_of Time, @twz diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb index e772d15d53..189e54f979 100644 --- a/activesupport/test/dependencies_test.rb +++ b/activesupport/test/dependencies_test.rb @@ -271,7 +271,8 @@ class DependenciesTest < ActiveSupport::TestCase    def test_raising_discards_autoloaded_constants      with_autoloading_fixtures do -      assert_raises(Exception, "arbitray exception message") { RaisesArbitraryException } +      e = assert_raises(Exception) { RaisesArbitraryException } +      assert_equal("arbitrary exception message", e.message)        assert_not defined?(A)        assert_not defined?(RaisesArbitraryException)      end @@ -397,14 +398,17 @@ class DependenciesTest < ActiveSupport::TestCase      end    end -  def failing_test_access_thru_and_upwards_fails -    with_autoloading_fixtures do -      assert_not defined?(ModuleFolder) -      assert_raise(NameError) { ModuleFolder::Object } -      assert_raise(NameError) { ModuleFolder::NestedClass::Object } +  # This raises only on 2.5.. (warns on ..2.4) +  if RUBY_VERSION > "2.5" +    def test_access_thru_and_upwards_fails +      with_autoloading_fixtures do +        assert_not defined?(ModuleFolder) +        assert_raise(NameError) { ModuleFolder::Object } +        assert_raise(NameError) { ModuleFolder::NestedClass::Object } +      end +    ensure +      remove_constants(:ModuleFolder)      end -  ensure -    remove_constants(:ModuleFolder)    end    def test_non_existing_const_raises_name_error_with_fully_qualified_name diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb index 5be93f3a1a..5f72fbf662 100644 --- a/activesupport/test/deprecation_test.rb +++ b/activesupport/test/deprecation_test.rb @@ -285,6 +285,16 @@ class DeprecationTest < ActiveSupport::TestCase      end    end +  def test_deprecated_constant_with_custom_message +    deprecator = deprecator_with_messages + +    klass = Class.new +    klass.const_set(:OLD, ActiveSupport::Deprecation::DeprecatedConstantProxy.new("klass::OLD", "Object", deprecator, message: "foo")) + +    klass::OLD.to_s +    assert_match "foo", deprecator.messages.last +  end +    def test_deprecated_instance_variable_with_instance_deprecator      deprecator = deprecator_with_messages diff --git a/activesupport/test/evented_file_update_checker_test.rb b/activesupport/test/evented_file_update_checker_test.rb index 77d8dcb0f8..f33a5f5764 100644 --- a/activesupport/test/evented_file_update_checker_test.rb +++ b/activesupport/test/evented_file_update_checker_test.rb @@ -7,6 +7,7 @@ class EventedFileUpdateCheckerTest < ActiveSupport::TestCase    def setup      skip if ENV["LISTEN"] == "0" +    require "listen"      super    end @@ -30,11 +31,6 @@ class EventedFileUpdateCheckerTest < ActiveSupport::TestCase      wait # wait for the events to fire    end -  def rm_f(files) -    super -    wait -  end -    test "notifies forked processes" do      jruby_skip "Forking not available on JRuby" @@ -43,7 +39,7 @@ class EventedFileUpdateCheckerTest < ActiveSupport::TestCase      checker = new_checker(tmpfiles) {}      assert !checker.updated? -    # Pipes used for flow controll across fork. +    # Pipes used for flow control across fork.      boot_reader,  boot_writer  = IO.pipe      touch_reader, touch_writer = IO.pipe diff --git a/activesupport/test/file_update_checker_shared_tests.rb b/activesupport/test/file_update_checker_shared_tests.rb index 48cd387196..361e7e2349 100644 --- a/activesupport/test/file_update_checker_shared_tests.rb +++ b/activesupport/test/file_update_checker_shared_tests.rb @@ -273,4 +273,10 @@ module FileUpdateCheckerSharedTests      assert checker.execute_if_updated      assert_equal 2, i    end + +  test "initialize raises an ArgumentError if no block given" do +    assert_raise ArgumentError do +      new_checker([]) +    end +  end  end diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb index d881bd346d..03d7b3fe94 100644 --- a/activesupport/test/inflector_test.rb +++ b/activesupport/test/inflector_test.rb @@ -31,6 +31,32 @@ class InflectorTest < ActiveSupport::TestCase      assert_equal "", ActiveSupport::Inflector.pluralize("")    end +  test "uncountability of ascii word" do +    word = "HTTP" +    ActiveSupport::Inflector.inflections do |inflect| +      inflect.uncountable word +    end + +    assert_equal word, ActiveSupport::Inflector.pluralize(word) +    assert_equal word, ActiveSupport::Inflector.singularize(word) +    assert_equal ActiveSupport::Inflector.pluralize(word), ActiveSupport::Inflector.singularize(word) + +    ActiveSupport::Inflector.inflections.uncountables.pop +  end + +  test "uncountability of non-ascii word" do +    word = "猫" +    ActiveSupport::Inflector.inflections do |inflect| +      inflect.uncountable word +    end + +    assert_equal word, ActiveSupport::Inflector.pluralize(word) +    assert_equal word, ActiveSupport::Inflector.singularize(word) +    assert_equal ActiveSupport::Inflector.pluralize(word), ActiveSupport::Inflector.singularize(word) + +    ActiveSupport::Inflector.inflections.uncountables.pop +  end +    ActiveSupport::Inflector.inflections.uncountable.each do |word|      define_method "test_uncountability_of_#{word}" do        assert_equal word, ActiveSupport::Inflector.singularize(word) @@ -245,35 +271,25 @@ class InflectorTest < ActiveSupport::TestCase      end    end -  # FIXME: get following tests to pass on jruby, currently skipped -  # -  # Currently this fails because ActiveSupport::Multibyte::Unicode#tidy_bytes -  # required a specific Encoding::Converter(UTF-8 to UTF8-MAC) which unavailable on JRuby -  # causing our tests to error out. -  # related bug http://jira.codehaus.org/browse/JRUBY-7194    def test_parameterize -    jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable"      StringToParameterized.each do |some_string, parameterized_string|        assert_equal(parameterized_string, ActiveSupport::Inflector.parameterize(some_string))      end    end    def test_parameterize_and_normalize -    jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable"      StringToParameterizedAndNormalized.each do |some_string, parameterized_string|        assert_equal(parameterized_string, ActiveSupport::Inflector.parameterize(some_string))      end    end    def test_parameterize_with_custom_separator -    jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable"      StringToParameterizeWithUnderscore.each do |some_string, parameterized_string|        assert_equal(parameterized_string, ActiveSupport::Inflector.parameterize(some_string, separator: "_"))      end    end    def test_parameterize_with_multi_character_separator -    jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable"      StringToParameterized.each do |some_string, parameterized_string|        assert_equal(parameterized_string.gsub("-", "__sep__"), ActiveSupport::Inflector.parameterize(some_string, separator: "__sep__"))      end @@ -507,12 +523,4 @@ class InflectorTest < ActiveSupport::TestCase        end      end    end - -  def test_inflections_with_uncountable_words -    ActiveSupport::Inflector.inflections do |inflect| -      inflect.uncountable "HTTP" -    end - -    assert_equal "HTTP", ActiveSupport::Inflector.pluralize("HTTP") -  end  end diff --git a/activesupport/test/json/decoding_test.rb b/activesupport/test/json/decoding_test.rb index 6d1d8f1b95..6f5051c312 100644 --- a/activesupport/test/json/decoding_test.rb +++ b/activesupport/test/json/decoding_test.rb @@ -75,12 +75,17 @@ class TestJSONDecoding < ActiveSupport::TestCase    }    TESTS.each_with_index do |(json, expected), index| +    fail_message = "JSON decoding failed for #{json}" +      test "json decodes #{index}" do        with_tz_default "Eastern Time (US & Canada)" do          with_parse_json_times(true) do            silence_warnings do -            assert_equal expected, ActiveSupport::JSON.decode(json), "JSON decoding \ -            failed for #{json}" +            if expected.nil? +              assert_nil ActiveSupport::JSON.decode(json), fail_message +            else +              assert_equal expected, ActiveSupport::JSON.decode(json), fail_message +            end            end          end        end diff --git a/activesupport/test/json/encoding_test_cases.rb b/activesupport/test/json/encoding_test_cases.rb index 8eac246937..b2f0cf3048 100644 --- a/activesupport/test/json/encoding_test_cases.rb +++ b/activesupport/test/json/encoding_test_cases.rb @@ -23,7 +23,7 @@ module JSONTest      end    end -  class MyStruct < Struct.new(:name, :value) +  MyStruct = Struct.new(:name, :value) do      def initialize(*)        @unused = "unused instance variable"        super diff --git a/activesupport/test/message_verifier_test.rb b/activesupport/test/message_verifier_test.rb index d56a46b250..d6109c761d 100644 --- a/activesupport/test/message_verifier_test.rb +++ b/activesupport/test/message_verifier_test.rb @@ -80,6 +80,6 @@ class MessageVerifierTest < ActiveSupport::TestCase      exception = assert_raise(ArgumentError) do        ActiveSupport::MessageVerifier.new(nil)      end -    assert_equal exception.message, "Secret should not be nil." +    assert_equal "Secret should not be nil.", exception.message    end  end diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb index 68ce42bf72..3c8494fe52 100644 --- a/activesupport/test/multibyte_chars_test.rb +++ b/activesupport/test/multibyte_chars_test.rb @@ -50,7 +50,7 @@ class MultibyteCharsTest < ActiveSupport::TestCase      assert_equal BYTE_STRING.length, BYTE_STRING.mb_chars.length    end -  def test_forwarded_method_with_non_string_result_should_be_returned_vertabim +  def test_forwarded_method_with_non_string_result_should_be_returned_verbatim      str = ""      str.singleton_class.class_eval { def __method_for_multibyte_testing_with_integer_result; 1; end }      @chars.wrapped_string.singleton_class.class_eval { def __method_for_multibyte_testing_with_integer_result; 1; end } diff --git a/activesupport/test/multibyte_test_helpers.rb b/activesupport/test/multibyte_test_helpers.rb index 2201860d8a..a70516bb08 100644 --- a/activesupport/test/multibyte_test_helpers.rb +++ b/activesupport/test/multibyte_test_helpers.rb @@ -18,7 +18,7 @@ module MultibyteTestHelpers    end    UNIDATA_URL = "http://www.unicode.org/Public/#{ActiveSupport::Multibyte::Unicode::UNICODE_VERSION}/ucd" -  CACHE_DIR = "#{Dir.tmpdir}/cache/unicode_conformance" +  CACHE_DIR = "#{Dir.tmpdir}/cache/unicode_conformance/#{ActiveSupport::Multibyte::Unicode::UNICODE_VERSION}"    FileUtils.mkdir_p(CACHE_DIR)    UNICODE_STRING = "こにちわ".freeze diff --git a/activesupport/test/number_helper_i18n_test.rb b/activesupport/test/number_helper_i18n_test.rb index 4f58e6607a..a1d1c41dc2 100644 --- a/activesupport/test/number_helper_i18n_test.rb +++ b/activesupport/test/number_helper_i18n_test.rb @@ -1,5 +1,6 @@  require "abstract_unit"  require "active_support/number_helper" +require "active_support/core_ext/hash/keys"  module ActiveSupport    class NumberHelperI18nTest < ActiveSupport::TestCase diff --git a/activesupport/test/reloader_test.rb b/activesupport/test/reloader_test.rb index 67d8c4b0e3..bdd80307c7 100644 --- a/activesupport/test/reloader_test.rb +++ b/activesupport/test/reloader_test.rb @@ -2,12 +2,15 @@ require "abstract_unit"  class ReloaderTest < ActiveSupport::TestCase    def test_prepare_callback -    prepared = false +    prepared = completed = false      reloader.to_prepare { prepared = true } +    reloader.to_complete { completed = true }      assert !prepared +    assert !completed      reloader.prepare!      assert prepared +    assert !completed      prepared = false      reloader.wrap do @@ -17,6 +20,15 @@ class ReloaderTest < ActiveSupport::TestCase      assert !prepared    end +  def test_prepend_prepare_callback +    i = 10 +    reloader.to_prepare { i += 1 } +    reloader.to_prepare(prepend: true) { i = 0 } + +    reloader.prepare! +    assert_equal 1, i +  end +    def test_only_run_when_check_passes      r = new_reloader { true }      invoked = false diff --git a/activesupport/test/safe_buffer_test.rb b/activesupport/test/safe_buffer_test.rb index e046b8d773..36c068b91f 100644 --- a/activesupport/test/safe_buffer_test.rb +++ b/activesupport/test/safe_buffer_test.rb @@ -175,6 +175,6 @@ class SafeBufferTest < ActiveSupport::TestCase    test "Should not affect frozen objects when accessing characters" do      x = "Hello".html_safe -    assert_equal x[/a/, 1], nil +    assert_nil x[/a/, 1]    end  end diff --git a/activesupport/test/security_utils_test.rb b/activesupport/test/security_utils_test.rb index 842bdd469d..e8f762da22 100644 --- a/activesupport/test/security_utils_test.rb +++ b/activesupport/test/security_utils_test.rb @@ -4,6 +4,11 @@ require "active_support/security_utils"  class SecurityUtilsTest < ActiveSupport::TestCase    def test_secure_compare_should_perform_string_comparison      assert ActiveSupport::SecurityUtils.secure_compare("a", "a") -    assert !ActiveSupport::SecurityUtils.secure_compare("a", "b") +    assert_not ActiveSupport::SecurityUtils.secure_compare("a", "b") +  end + +  def test_variable_size_secure_compare_should_perform_string_comparison +    assert ActiveSupport::SecurityUtils.variable_size_secure_compare("a", "a") +    assert_not ActiveSupport::SecurityUtils.variable_size_secure_compare("a", "b")    end  end diff --git a/activesupport/test/string_inquirer_test.rb b/activesupport/test/string_inquirer_test.rb index d41e4d6800..79a715349c 100644 --- a/activesupport/test/string_inquirer_test.rb +++ b/activesupport/test/string_inquirer_test.rb @@ -20,4 +20,25 @@ class StringInquirerTest < ActiveSupport::TestCase    def test_respond_to      assert_respond_to @string_inquirer, :development?    end + +  def test_respond_to_fallback_to_string_respond_to +    String.class_eval do +      def respond_to_missing?(name, include_private = false) +        (name == :bar) || super +      end +    end +    str = ActiveSupport::StringInquirer.new("hello") + +    assert_respond_to str, :are_you_ready? +    assert_respond_to str, :bar +    assert_not_respond_to str, :nope + +  ensure +    String.class_eval do +      undef_method :respond_to_missing? +      def respond_to_missing?(name, include_private = false) +        super +      end +    end +  end  end diff --git a/activesupport/test/time_travel_test.rb b/activesupport/test/time_travel_test.rb index c68be329bc..e0d3fb0cf5 100644 --- a/activesupport/test/time_travel_test.rb +++ b/activesupport/test/time_travel_test.rb @@ -3,6 +3,10 @@ require "active_support/core_ext/date_time"  require "active_support/core_ext/numeric/time"  class TimeTravelTest < ActiveSupport::TestCase +  class TimeSubclass < ::Time; end +  class DateSubclass < ::Date; end +  class DateTimeSubclass < ::DateTime; end +    def test_time_helper_travel      Time.stub(:now, Time.now) do        begin @@ -90,11 +94,12 @@ class TimeTravelTest < ActiveSupport::TestCase        outer_expected_time = Time.new(2004, 11, 24, 01, 04, 44)        inner_expected_time = Time.new(2004, 10, 24, 01, 04, 44)        travel_to outer_expected_time do -        assert_raises(RuntimeError, /Calling `travel_to` with a block, when we have previously already made a call to `travel_to`, can lead to confusing time stubbing./) do +        e = assert_raises(RuntimeError) do            travel_to(inner_expected_time) do              #noop            end          end +        assert_match(/Calling `travel_to` with a block, when we have previously already made a call to `travel_to`, can lead to confusing time stubbing./, e.message)        end      end    end @@ -142,4 +147,19 @@ class TimeTravelTest < ActiveSupport::TestCase        end      end    end + +  def test_time_helper_travel_with_time_subclass +    assert_equal TimeSubclass, TimeSubclass.now.class +    assert_equal DateSubclass, DateSubclass.today.class +    assert_equal DateTimeSubclass, DateTimeSubclass.now.class + +    travel 1.day do +      assert_equal TimeSubclass, TimeSubclass.now.class +      assert_equal DateSubclass, DateSubclass.today.class +      assert_equal DateTimeSubclass, DateTimeSubclass.now.class +      assert_equal Time.now.to_s, TimeSubclass.now.to_s +      assert_equal Date.today.to_s, DateSubclass.today.to_s +      assert_equal DateTime.now.to_s, DateTimeSubclass.now.to_s +    end +  end  end diff --git a/activesupport/test/transliterate_test.rb b/activesupport/test/transliterate_test.rb index 040ddd25fc..466b69bcef 100644 --- a/activesupport/test/transliterate_test.rb +++ b/activesupport/test/transliterate_test.rb @@ -31,4 +31,22 @@ class TransliterateTest < ActiveSupport::TestCase    def test_transliterate_should_allow_a_custom_replacement_char      assert_equal "a*b", ActiveSupport::Inflector.transliterate("a索b", "*")    end + +  def test_transliterate_handles_empty_string +    assert_equal "", ActiveSupport::Inflector.transliterate("") +  end + +  def test_transliterate_handles_nil +    exception = assert_raises ArgumentError do +      ActiveSupport::Inflector.transliterate(nil) +    end +    assert_equal "Can only transliterate strings. Received NilClass", exception.message +  end + +  def test_transliterate_handles_unknown_object +    exception = assert_raises ArgumentError do +      ActiveSupport::Inflector.transliterate(Object.new) +    end +    assert_equal "Can only transliterate strings. Received Object", exception.message +  end  end diff --git a/guides/Rakefile b/guides/Rakefile index f241cf5918..d2591f523c 100644 --- a/guides/Rakefile +++ b/guides/Rakefile @@ -13,7 +13,7 @@ namespace :guides do      desc "Generate .mobi file. The kindlegen executable must be in your PATH. You can get it for free from http://www.amazon.com/gp/feature.html?docId=1000765211"      task :kindle do -      require 'kindlerb' +      require "kindlerb"        unless Kindlerb.kindlegen_available?          abort "Please run `setupkindlerb` to install kindlegen"        end diff --git a/guides/bug_report_templates/action_controller_gem.rb b/guides/bug_report_templates/action_controller_gem.rb index 960d269d90..85d698f81b 100644 --- a/guides/bug_report_templates/action_controller_gem.rb +++ b/guides/bug_report_templates/action_controller_gem.rb @@ -8,7 +8,7 @@ end  gemfile(true) do    source "https://rubygems.org"    # Activate the gem you are reporting the issue against. -  gem "rails", "5.0.0" +  gem "rails", "5.0.1"  end  require "rack/test" diff --git a/guides/bug_report_templates/action_controller_master.rb b/guides/bug_report_templates/action_controller_master.rb index 486c7243ad..7644f6fe4a 100644 --- a/guides/bug_report_templates/action_controller_master.rb +++ b/guides/bug_report_templates/action_controller_master.rb @@ -8,6 +8,7 @@ end  gemfile(true) do    source "https://rubygems.org"    gem "rails", github: "rails/rails" +  gem "arel", github: "rails/arel"  end  require "action_controller/railtie" diff --git a/guides/bug_report_templates/active_job_gem.rb b/guides/bug_report_templates/active_job_gem.rb index debc46ad54..f715caec95 100644 --- a/guides/bug_report_templates/active_job_gem.rb +++ b/guides/bug_report_templates/active_job_gem.rb @@ -8,7 +8,7 @@ end  gemfile(true) do    source "https://rubygems.org"    # Activate the gem you are reporting the issue against. -  gem "activejob", "5.0.0" +  gem "activejob", "5.0.1"  end  require "minitest/autorun" diff --git a/guides/bug_report_templates/active_job_master.rb b/guides/bug_report_templates/active_job_master.rb index f61518713f..7591470440 100644 --- a/guides/bug_report_templates/active_job_master.rb +++ b/guides/bug_report_templates/active_job_master.rb @@ -8,6 +8,7 @@ end  gemfile(true) do    source "https://rubygems.org"    gem "rails", github: "rails/rails" +  gem "arel", github: "rails/arel"  end  require "active_job" diff --git a/guides/bug_report_templates/active_record_gem.rb b/guides/bug_report_templates/active_record_gem.rb index e18302fe65..98edcb76b1 100644 --- a/guides/bug_report_templates/active_record_gem.rb +++ b/guides/bug_report_templates/active_record_gem.rb @@ -8,7 +8,7 @@ end  gemfile(true) do    source "https://rubygems.org"    # Activate the gem you are reporting the issue against. -  gem "activerecord", "5.0.0" +  gem "activerecord", "5.0.1"    gem "sqlite3"  end diff --git a/guides/bug_report_templates/active_record_master.rb b/guides/bug_report_templates/active_record_master.rb index 7265a671b0..8bbc1ef19e 100644 --- a/guides/bug_report_templates/active_record_master.rb +++ b/guides/bug_report_templates/active_record_master.rb @@ -8,6 +8,7 @@ end  gemfile(true) do    source "https://rubygems.org"    gem "rails", github: "rails/rails" +  gem "arel", github: "rails/arel"    gem "sqlite3"  end diff --git a/guides/bug_report_templates/active_record_migrations_gem.rb b/guides/bug_report_templates/active_record_migrations_gem.rb index ba80e6b4ad..ece6ae4f12 100644 --- a/guides/bug_report_templates/active_record_migrations_gem.rb +++ b/guides/bug_report_templates/active_record_migrations_gem.rb @@ -8,7 +8,7 @@ end  gemfile(true) do    source "https://rubygems.org"    # Activate the gem you are reporting the issue against. -  gem "activerecord", "5.0.0.1" +  gem "activerecord", "5.0.1"    gem "sqlite3"  end diff --git a/guides/bug_report_templates/active_record_migrations_master.rb b/guides/bug_report_templates/active_record_migrations_master.rb index 13a375d1ba..84a4b71909 100644 --- a/guides/bug_report_templates/active_record_migrations_master.rb +++ b/guides/bug_report_templates/active_record_migrations_master.rb @@ -8,6 +8,7 @@ end  gemfile(true) do    source "https://rubygems.org"    gem "rails", github: "rails/rails" +  gem "arel", github: "rails/arel"    gem "sqlite3"  end diff --git a/guides/bug_report_templates/benchmark.rb b/guides/bug_report_templates/benchmark.rb index 54433b34dd..a0b541d012 100644 --- a/guides/bug_report_templates/benchmark.rb +++ b/guides/bug_report_templates/benchmark.rb @@ -8,6 +8,7 @@ end  gemfile(true) do    source "https://rubygems.org"    gem "rails", github: "rails/rails" +  gem "arel", github: "rails/arel"    gem "benchmark-ips"  end diff --git a/guides/bug_report_templates/generic_gem.rb b/guides/bug_report_templates/generic_gem.rb index a94848e25b..fa9f94eea0 100644 --- a/guides/bug_report_templates/generic_gem.rb +++ b/guides/bug_report_templates/generic_gem.rb @@ -8,7 +8,7 @@ end  gemfile(true) do    source "https://rubygems.org"    # Activate the gem you are reporting the issue against. -  gem "activesupport", "5.0.0" +  gem "activesupport", "5.0.1"  end  require "active_support/core_ext/object/blank" diff --git a/guides/bug_report_templates/generic_master.rb b/guides/bug_report_templates/generic_master.rb index d3a7ae4ac4..ed45726e92 100644 --- a/guides/bug_report_templates/generic_master.rb +++ b/guides/bug_report_templates/generic_master.rb @@ -8,6 +8,7 @@ end  gemfile(true) do    source "https://rubygems.org"    gem "rails", github: "rails/rails" +  gem "arel", github: "rails/arel"  end  require "active_support" diff --git a/guides/rails_guides/kindle.rb b/guides/rails_guides/kindle.rb index 5ac802c817..9536d0bd3b 100644 --- a/guides/rails_guides/kindle.rb +++ b/guides/rails_guides/kindle.rb @@ -1,6 +1,6 @@  #!/usr/bin/env ruby -require 'kindlerb' +require "kindlerb"  require "nokogiri"  require "fileutils"  require "yaml" diff --git a/guides/source/5_0_release_notes.md b/guides/source/5_0_release_notes.md index 39753cbd6f..24fb0ca6e1 100644 --- a/guides/source/5_0_release_notes.md +++ b/guides/source/5_0_release_notes.md @@ -499,6 +499,9 @@ Please refer to the [Changelog][action-view] for detailed changes.      `datetime-local`.      ([Pull Request](https://github.com/rails/rails/pull/25469)) +*   Allow blocks while rendering with the `render partial:` helper. +    ([Pull Request](https://github.com/rails/rails/pull/17974)) +  Action Mailer  ------------- diff --git a/guides/source/action_cable_overview.md b/guides/source/action_cable_overview.md index 319277ef68..e929945dd0 100644 --- a/guides/source/action_cable_overview.md +++ b/guides/source/action_cable_overview.md @@ -240,8 +240,8 @@ WebNotificationsChannel.broadcast_to(  ```  The `WebNotificationsChannel.broadcast_to` call places a message in the current -subscription adapter (by default `redis` for production and `async` for development and  -test environments)'s pubsub queue under a separate broadcasting name for each user.  +subscription adapter (by default `redis` for production and `async` for development and +test environments)'s pubsub queue under a separate broadcasting name for each user.  For a user with an ID of 1, the broadcasting name would be `web_notifications:1`.  The channel has been instructed to stream everything that arrives at @@ -530,7 +530,7 @@ Action Cable has two required configurations: a subscription adapter and allowed  ### Subscription Adapter  By default, Action Cable looks for a configuration file in `config/cable.yml`. -The file must specify an adapter and a URL for each Rails environment. See the +The file must specify an adapter for each Rails environment. See the  [Dependencies](#dependencies) section for additional information on adapters.  ```yaml @@ -543,7 +543,28 @@ test:  production:    adapter: redis    url: redis://10.10.3.153:6381 +  channel_prefix: appname_production  ``` +#### Adapter Configuration + +Below is a list of the subscription adapters available for end users. + +##### Async Adapter + +The async adapter is intended for development/testing and should not be used in production. + +##### Redis Adapter + +Action Cable contains two Redis adapters: "normal" Redis and Evented Redis. Both +of the adapters require users to provide a URL pointing to the Redis server. +Additionally, a channel_prefix may be provided to avoid channel name collisions +when using the same Redis server for multiple applications. See the [Redis PubSub documentation](https://redis.io/topics/pubsub#database-amp-scoping) for more details. + +##### PostgreSQL Adapter + +The PostgreSQL adapter uses Active Record's connection pool, and thus the +application's `config/database.yml` database configuration, for its connection. +This may change in the future. [#27214](https://github.com/rails/rails/issues/27214)  ### Allowed Request Origins diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md index 0825d54cb7..380fdac658 100644 --- a/guides/source/action_mailer_basics.md +++ b/guides/source/action_mailer_basics.md @@ -160,8 +160,8 @@ When you call the `mail` method now, Action Mailer will detect the two templates  #### Calling the Mailer  Mailers are really just another way to render a view. Instead of rendering a -view and sending out the HTTP protocol, they are just sending it out through the -email protocols instead. Due to this, it makes sense to just have your +view and sending it over the HTTP protocol, they are just sending it out through +the email protocols instead. Due to this, it makes sense to just have your  controller tell the Mailer to send an email when a user is successfully created.  Setting this up is painfully simple. @@ -525,7 +525,7 @@ By using the full URL, your links will now work in your emails.  #### Generating URLs with `url_for` -`url_for` generate full URL by default in templates. +`url_for` generates a full URL by default in templates.  If you did not configure the `:host` option globally make sure to pass it to  `url_for`. @@ -574,7 +574,7 @@ Now you can display an image inside your email.  ### Sending Multipart Emails  Action Mailer will automatically send multipart emails if you have different -templates for the same action. So, for our UserMailer example, if you have +templates for the same action. So, for our `UserMailer` example, if you have  `welcome_email.text.erb` and `welcome_email.html.erb` in  `app/views/user_mailer`, Action Mailer will automatically send a multipart email  with the HTML and text versions setup as different parts. diff --git a/guides/source/active_record_callbacks.md b/guides/source/active_record_callbacks.md index 868daf2435..666d987f8c 100644 --- a/guides/source/active_record_callbacks.md +++ b/guides/source/active_record_callbacks.md @@ -202,11 +202,9 @@ The following methods trigger callbacks:  * `create`  * `create!` -* `decrement!`  * `destroy`  * `destroy!`  * `destroy_all` -* `increment!`  * `save`  * `save!`  * `save(validate: false)` diff --git a/guides/source/active_record_postgresql.md b/guides/source/active_record_postgresql.md index 58af2f82b3..6d07291b07 100644 --- a/guides/source/active_record_postgresql.md +++ b/guides/source/active_record_postgresql.md @@ -111,7 +111,7 @@ profile.settings = {"color" => "yellow", "resolution" => "1280x1024"}  profile.save!  Profile.where("settings->'color' = ?", "yellow") -#=> #<ActiveRecord::Relation [#<Profile id: 1, settings: {"color"=>"yellow", "resolution"=>"1280x1024"}>]> +# => #<ActiveRecord::Relation [#<Profile id: 1, settings: {"color"=>"yellow", "resolution"=>"1280x1024"}>]>  ```  ### JSON diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md index 6bbc79a326..67bed4c8da 100644 --- a/guides/source/active_support_core_extensions.md +++ b/guides/source/active_support_core_extensions.md @@ -135,36 +135,53 @@ NOTE: Defined in `active_support/core_ext/object/blank.rb`.  ### `duplicable?` -A few fundamental objects in Ruby are singletons. For example, in the whole life of a program the integer 1 refers always to the same instance: +In Ruby 2.4 most objects can be duplicated via `dup` or `clone` except  +methods and certain numbers. Though Ruby 2.2 and 2.3 can't duplicate `nil`, +`false`, `true`, and  symbols as well as instances `Float`, `Fixnum`,  +and `Bignum` instances.  ```ruby -1.object_id                 # => 3 -Math.cos(0).to_i.object_id  # => 3 +"foo".dup           # => "foo" +"".dup              # => "" +1.method(:+).dup    # => TypeError: allocator undefined for Method +Complex(0).dup      # => TypeError: can't copy Complex  ``` -Hence, there's no way these objects can be duplicated through `dup` or `clone`: +Active Support provides `duplicable?` to query an object about this:  ```ruby -true.dup  # => TypeError: can't dup TrueClass +"foo".duplicable?           # => true +"".duplicable?              # => true +Rational(1).duplicable?     # => false +Complex(1).duplicable?      # => false +1.method(:+).duplicable?    # => false  ``` -Some numbers which are not singletons are not duplicable either: +`duplicable?` matches Ruby's `dup` according to the Ruby version. + +So in 2.4:  ```ruby -0.0.clone        # => allocator undefined for Float -(2**1024).clone  # => allocator undefined for Bignum +nil.dup                 # => nil +:my_symbol.dup          # => :my_symbol +1.dup                   # => 1 + +nil.duplicable?         # => true +:my_symbol.duplicable?  # => true +1.duplicable?           # => true  ``` -Active Support provides `duplicable?` to programmatically query an object about this property: +Whereas in 2.2 and 2.3:  ```ruby -"foo".duplicable? # => true -"".duplicable?    # => true -0.0.duplicable?   # => false -false.duplicable? # => false -``` +nil.dup                 # => TypeError: can't dup NilClass +:my_symbol.dup          # => TypeError: can't dup Symbol +1.dup                   # => TypeError: can't dup Fixnum -By definition all objects are `duplicable?` except `nil`, `false`, `true`, symbols, numbers, class, module, and method objects. +nil.duplicable?         # => false +:my_symbol.duplicable?  # => false +1.duplicable?           # => false +```  WARNING: Any class can disallow duplication by removing `dup` and `clone` or raising exceptions from them. Thus only `rescue` can tell whether a given arbitrary object is duplicable. `duplicable?` depends on the hard-coded list above, but it is much faster than `rescue`. Use it only if you know the hard-coded list is enough in your use case. diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md index 25717e04e4..360de9a584 100644 --- a/guides/source/asset_pipeline.md +++ b/guides/source/asset_pipeline.md @@ -78,9 +78,9 @@ requests can mean faster loading for your application.  Sprockets concatenates all JavaScript files into one master `.js` file and all  CSS files into one master `.css` file. As you'll learn later in this guide, you  can customize this strategy to group files any way you like. In production, -Rails inserts an MD5 fingerprint into each filename so that the file is cached -by the web browser. You can invalidate the cache by altering this fingerprint, -which happens automatically whenever you change the file contents. +Rails inserts an SHA256 fingerprint into each filename so that the file is +cached by the web browser. You can invalidate the cache by altering this +fingerprint, which happens automatically whenever you change the file contents.  The second feature of the asset pipeline is asset minification or compression.  For CSS files, this is done by removing whitespace and comments. For JavaScript, @@ -106,7 +106,7 @@ or in web browsers) to keep their own copy of the content. When the content is  updated, the fingerprint will change. This will cause the remote clients to  request a new copy of the content. This is generally known as _cache busting_. -The technique sprockets uses for fingerprinting is to insert a hash of the +The technique Sprockets uses for fingerprinting is to insert a hash of the  content into the name, usually at the end. For example a CSS file `global.css`  ``` @@ -346,9 +346,9 @@ Provided that the pipeline is enabled within your application (and not disabled  in the current environment context), this file is served by Sprockets. If a file  exists at `public/assets/rails.png` it is served by the web server. -Alternatively, a request for a file with an MD5 hash such as -`public/assets/rails-af27b6a414e6da00003503148be9b409.png` is treated the same -way. How these hashes are generated is covered in the [In +Alternatively, a request for a file with an SHA256 hash such as +`public/assets/rails-f90d8a84c707a8dc923fca1ca1895ae8ed0a09237f6992015fef1e11be77c023.png` +is treated the same way. How these hashes are generated is covered in the [In  Production](#in-production) section later on in this guide.  Sprockets will also look through the paths specified in `config.assets.paths`, @@ -654,7 +654,7 @@ In the production environment Sprockets uses the fingerprinting scheme outlined  above. By default Rails assumes assets have been precompiled and will be  served as static assets by your web server. -During the precompilation phase an MD5 is generated from the contents of the +During the precompilation phase an SHA256 is generated from the contents of the  compiled files, and inserted into the filenames as they are written to disk.  These fingerprinted names are used by the Rails helpers in place of the manifest  name. @@ -743,22 +743,24 @@ Rails.application.config.assets.precompile += %w( admin.js admin.css )  NOTE. Always specify an expected compiled filename that ends with .js or .css,  even if you want to add Sass or CoffeeScript files to the precompile array. -The task also generates a `manifest-md5hash.json` that contains a list with -all your assets and their respective fingerprints. This is used by the Rails -helper methods to avoid handing the mapping requests back to Sprockets. A -typical manifest file looks like: +The task also generates a `.sprockets-manifest-md5hash.json` (where `md5hash` is +an MD5 hash) that contains a list with all your assets and their respective +fingerprints. This is used by the Rails helper methods to avoid handing the +mapping requests back to Sprockets. A typical manifest file looks like:  ```ruby -{"files":{"application-723d1be6cc741a3aabb1cec24276d681.js":{"logical_path":"application.js","mtime":"2013-07-26T22:55:03-07:00","size":302506, -"digest":"723d1be6cc741a3aabb1cec24276d681"},"application-12b3c7dd74d2e9df37e7cbb1efa76a6d.css":{"logical_path":"application.css","mtime":"2013-07-26T22:54:54-07:00","size":1560, -"digest":"12b3c7dd74d2e9df37e7cbb1efa76a6d"},"application-1c5752789588ac18d7e1a50b1f0fd4c2.css":{"logical_path":"application.css","mtime":"2013-07-26T22:56:17-07:00","size":1591, -"digest":"1c5752789588ac18d7e1a50b1f0fd4c2"},"favicon-a9c641bf2b81f0476e876f7c5e375969.ico":{"logical_path":"favicon.ico","mtime":"2013-07-26T23:00:10-07:00","size":1406, -"digest":"a9c641bf2b81f0476e876f7c5e375969"},"my_image-231a680f23887d9dd70710ea5efd3c62.png":{"logical_path":"my_image.png","mtime":"2013-07-26T23:00:27-07:00","size":6646, -"digest":"231a680f23887d9dd70710ea5efd3c62"}},"assets":{"application.js": -"application-723d1be6cc741a3aabb1cec24276d681.js","application.css": -"application-1c5752789588ac18d7e1a50b1f0fd4c2.css", -"favicon.ico":"favicona9c641bf2b81f0476e876f7c5e375969.ico","my_image.png": -"my_image-231a680f23887d9dd70710ea5efd3c62.png"}} +{"files":{"application-aee4be71f1288037ae78b997df388332edfd246471b533dcedaa8f9fe156442b.js":{"logical_path":"application.js","mtime":"2016-12-23T20:12:03-05:00","size":412383, +"digest":"aee4be71f1288037ae78b997df388332edfd246471b533dcedaa8f9fe156442b","integrity":"sha256-ruS+cfEogDeueLmX3ziDMu39JGRxtTPc7aqPn+FWRCs="}, +"application-86a292b5070793c37e2c0e5f39f73bb387644eaeada7f96e6fc040a028b16c18.css":{"logical_path":"application.css","mtime":"2016-12-23T19:12:20-05:00","size":2994, +"digest":"86a292b5070793c37e2c0e5f39f73bb387644eaeada7f96e6fc040a028b16c18","integrity":"sha256-hqKStQcHk8N+LA5fOfc7s4dkTq6tp/lub8BAoCixbBg="}, +"favicon-8d2387b8d4d32cecd93fa3900df0e9ff89d01aacd84f50e780c17c9f6b3d0eda.ico":{"logical_path":"favicon.ico","mtime":"2016-12-23T20:11:00-05:00","size":8629, +"digest":"8d2387b8d4d32cecd93fa3900df0e9ff89d01aacd84f50e780c17c9f6b3d0eda","integrity":"sha256-jSOHuNTTLOzZP6OQDfDp/4nQGqzYT1DngMF8n2s9Dto="}, +"my_image-f4028156fd7eca03584d5f2fc0470df1e0dbc7369eaae638b2ff033f988ec493.png":{"logical_path":"my_image.png","mtime":"2016-12-23T20:10:54-05:00","size":23414, +"digest":"f4028156fd7eca03584d5f2fc0470df1e0dbc7369eaae638b2ff033f988ec493","integrity":"sha256-9AKBVv1+ygNYTV8vwEcN8eDbxzaequY4sv8DP5iOxJM="}}, +"assets":{"application.js":"application-aee4be71f1288037ae78b997df388332edfd246471b533dcedaa8f9fe156442b.js", +"application.css":"application-86a292b5070793c37e2c0e5f39f73bb387644eaeada7f96e6fc040a028b16c18.css", +"favicon.ico":"favicon-8d2387b8d4d32cecd93fa3900df0e9ff89d01aacd84f50e780c17c9f6b3d0eda.ico", +"my_image.png":"my_image-f4028156fd7eca03584d5f2fc0470df1e0dbc7369eaae638b2ff033f988ec493.png"}}  ```  The default location for the manifest is the root of the location specified in @@ -850,7 +852,7 @@ config.assets.compile = true  On the first request the assets are compiled and cached as outlined in  development above, and the manifest names used in the helpers are altered to -include the MD5 hash. +include the SHA256 hash.  Sprockets also sets the `Cache-Control` HTTP header to `max-age=31536000`. This  signals all caches between your server and the client browser that this content diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md index 03d3daecc8..6e68935f9b 100644 --- a/guides/source/association_basics.md +++ b/guides/source/association_basics.md @@ -709,55 +709,73 @@ class Book < ApplicationRecord  end  ``` -By default, Active Record doesn't know about the connection between these associations. This can lead to two copies of an object getting out of sync: +Active Record will attempt to automatically identify that these two models share a bi-directional association based on the association name. In this way, Active Record will only load one copy of the `Author` object, making your application more efficient and preventing inconsistent data:  ```ruby  a = Author.first  b = a.books.first  a.first_name == b.author.first_name # => true -a.first_name = 'Manny' -a.first_name == b.author.first_name # => false +a.first_name = 'David' +a.first_name == b.author.first_name # => true  ``` -This happens because `a` and `b.author` are two different in-memory representations of the same data, and neither one is automatically refreshed from changes to the other. Active Record provides the `:inverse_of` option so that you can inform it of these relations: +Active Record supports automatic identification for most associations with standard names. However, Active Record will not automatically identify bi-directional associations that contain any of the following options: + +* `:conditions` +* `:through` +* `:polymorphic` +* `:class_name` +* `:foreign_key` + +For example, consider the following model declarations:  ```ruby  class Author < ApplicationRecord -  has_many :books, inverse_of: :author +  has_many :books  end  class Book < ApplicationRecord -  belongs_to :author, inverse_of: :books +  belongs_to :writer, class_name: 'Author', foreign_key: 'author_id'  end  ``` -With these changes, Active Record will only load one copy of the author object, preventing inconsistencies and making your application more efficient: +Active Record will no longer automatically recognize the bi-directional association:  ```ruby  a = Author.first  b = a.books.first -a.first_name == b.author.first_name # => true -a.first_name = 'Manny' -a.first_name == b.author.first_name # => true +a.first_name == b.writer.first_name # => true +a.first_name = 'David' +a.first_name == b.writer.first_name # => false +``` + +Active Record provides the `:inverse_of` option so you can explicitly declare bi-directional associations: + +```ruby +class Author < ApplicationRecord +  has_many :books, inverse_of: 'writer' +end + +class Book < ApplicationRecord +  belongs_to :writer, class_name: 'Author', foreign_key: 'author_id' +end  ``` -There are a few limitations to `inverse_of` support: +By including the `:inverse_of` option in the `has_many` association declaration, Active Record will now recognize the bi-directional association: + +```ruby +a = Author.first +b = a.books.first +a.first_name == b.writer.first_name # => true +a.first_name = 'David' +a.first_name == b.writer.first_name # => true +``` + +There are a few limitations to `:inverse_of` support:  * They do not work with `:through` associations.  * They do not work with `:polymorphic` associations.  * They do not work with `:as` associations. -* For `belongs_to` associations, `has_many` inverse associations are ignored. - -Every association will attempt to automatically find the inverse association -and set the `:inverse_of` option heuristically (based on the association name). -Most associations with standard names will be supported. However, associations -that contain the following options will not have their inverses set -automatically: - -* `:conditions` -* `:through` -* `:polymorphic` -* `:foreign_key`  Detailed Association Reference  ------------------------------ diff --git a/guides/source/command_line.md b/guides/source/command_line.md index 9d7ecce947..c8d559745e 100644 --- a/guides/source/command_line.md +++ b/guides/source/command_line.md @@ -407,8 +407,8 @@ db:fixtures:load                    Loads fixtures into the ...  db:migrate                          Migrate the database ...  db:migrate:status                   Display status of migrations  db:rollback                         Rolls the schema back to ... -db:schema:cache:clear               Clears a db/schema_cache.dump file -db:schema:cache:dump                Creates a db/schema_cache.dump file +db:schema:cache:clear               Clears a db/schema_cache.yml file +db:schema:cache:dump                Creates a db/schema_cache.yml file  db:schema:dump                      Creates a db/schema.rb file ...  db:schema:load                      Loads a schema.rb file ...  db:seed                             Loads the seed data ... diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md index 830a546570..fe5437ae5d 100644 --- a/guides/source/contributing_to_ruby_on_rails.md +++ b/guides/source/contributing_to_ruby_on_rails.md @@ -67,7 +67,7 @@ can expect it to be marked "invalid" as soon as it's reviewed.  Sometimes, the line between 'bug' and 'feature' is a hard one to draw.  Generally, a feature is anything that adds new behavior, while a bug is  anything that causes incorrect behavior. Sometimes, -the core team will have to make a judgement call. That said, the distinction +the core team will have to make a judgment call. That said, the distinction  generally just affects which release your patch will get in to; we love feature  submissions! They just won't get backported to maintenance branches. @@ -335,10 +335,12 @@ file.  #### Testing Active Record -First, create the databases you'll need. For MySQL and PostgreSQL, -running the SQL statements `create database activerecord_unittest` and -`create database activerecord_unittest2` is sufficient. This is not -necessary for SQLite3. +First, create the databases you'll need. You can find a list of the required  +table names, usernames, and passwords in `activerecord/test/config.example.yml`. + +For MySQL and PostgreSQL, running the SQL statements `create database +activerecord_unittest` and `create database activerecord_unittest2` is +sufficient. This is not necessary for SQLite3.  This is how you run the Active Record test suite only for SQLite3: diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md index df3003a6a8..ba0cdbf3af 100644 --- a/guides/source/debugging_rails_applications.md +++ b/guides/source/debugging_rails_applications.md @@ -683,7 +683,7 @@ Ruby instruction to be executed -- in this case, Active Support's `week` method.     51:   #     52:   #   2.weeks # => 14 days     53:   def weeks -=> 54:     ActiveSupport::Duration.new(self * 7.days, [[:days, self * 7]]) +=> 54:     ActiveSupport::Duration.weeks(self)     55:   end     56:   alias :week :weeks     57: diff --git a/guides/source/i18n.md b/guides/source/i18n.md index fd54bca4ff..0b7cc055be 100644 --- a/guides/source/i18n.md +++ b/guides/source/i18n.md @@ -72,11 +72,13 @@ I18n.l Time.now  There are also attribute readers and writers for the following attributes:  ```ruby -load_path         # Announce your custom translation files -locale            # Get and set the current locale -default_locale    # Get and set the default locale -exception_handler # Use a different exception_handler -backend           # Use a different backend +load_path                 # Announce your custom translation files +locale                    # Get and set the current locale +default_locale            # Get and set the default locale +available_locales         # Whitelist locales available for the application +enforce_available_locales # Enforce locale whitelisting (true or false) +exception_handler         # Use a different exception_handler +backend                   # Use a different backend  ```  So, let's internationalize a simple Rails application from the ground up in the next chapters! @@ -124,6 +126,9 @@ The load path must be specified before any translations are looked up. To change  # Where the I18n library should search for translation files  I18n.load_path += Dir[Rails.root.join('lib', 'locale', '*.{rb,yml}')] +# Whitelist locales available for the application +I18n.available_locales = [:en, :pt] +  # Set default locale to something other than :en  I18n.default_locale = :pt  ``` @@ -404,6 +409,35 @@ NOTE: You need to restart the server when you add new locale files.  You may use YAML (`.yml`) or plain Ruby (`.rb`) files for storing your translations in SimpleStore. YAML is the preferred option among Rails developers. However, it has one big disadvantage. YAML is very sensitive to whitespace and special characters, so the application may not load your dictionary properly. Ruby files will crash your application on first request, so you may easily find what's wrong. (If you encounter any "weird issues" with YAML dictionaries, try putting the relevant portion of your dictionary into a Ruby file.) +If your translations are stored in YAML files, certain keys must be escaped. They are: + +* true, on, yes +* false, off, no + +Examples: + +```erb +# confing/locales/en.yml +en: +  success: +    'true':  'True!' +    'on':    'On!' +    'false': 'False!' +  failure: +    true:    'True!' +    off:     'Off!' +    false:   'False!' +``` + +```ruby +I18n.t 'success.true' # => 'True!' +I18n.t 'success.on' # => 'On!' +I18n.t 'success.false' # => 'False!' +I18n.t 'failure.false' # => Translation Missing +I18n.t 'failure.off' # => Translation Missing +I18n.t 'failure.true' # => Translation Missing +``` +  ### Passing Variables to Translations  One key consideration for successfully internationalizing an application is to @@ -667,7 +701,7 @@ end  ### Pluralization -In English there are only one singular and one plural form for a given string, e.g. "1 message" and "2 messages". Other languages ([Arabic](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html#ar), [Japanese](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html#ja), [Russian](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html#ru) and many more) have different grammars that have additional or fewer [plural forms](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html). Thus, the I18n API provides a flexible pluralization feature. +In English there are only one singular and one plural form for a given string, e.g. "1 message" and "2 messages". Other languages ([Arabic](http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#ar), [Japanese](http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#ja), [Russian](http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html#ru) and many more) have different grammars that have additional or fewer [plural forms](http://cldr.unicode.org/index/cldr-spec/plural-rules). Thus, the I18n API provides a flexible pluralization feature.  The `:count` interpolation variable has a special role in that it both is interpolated to the translation and used to pick a pluralization from the translations according to the pluralization rules defined by CLDR: diff --git a/guides/source/initialization.md b/guides/source/initialization.md index aa7bbcc19b..3ea156c6fe 100644 --- a/guides/source/initialization.md +++ b/guides/source/initialization.md @@ -168,7 +168,7 @@ module Rails::Command    class << self      def invoke(namespace, args = [], **config)        namespace = namespace.to_s -      namespace = "help" if namespace.blank? || Thor::HELP_MAPPINGS.include?(namespace) +      namespace = "help" if namespace.blank? || HELP_MAPPINGS.include?(namespace)        namespace = "version" if %w( -v --version ).include? namespace        if command = find_by_namespace(namespace) diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md index c8702f54fc..293c99fc8f 100644 --- a/guides/source/layouts_and_rendering.md +++ b/guides/source/layouts_and_rendering.md @@ -1155,7 +1155,7 @@ To pass a local variable to a partial in only specific cases use the `local_assi    <%= render article, full: true %>    ``` -* `_articles.html.erb` +* `_article.html.erb`    ```erb    <h2><%= article.title %></h2> diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md index dda2b12a3a..8a3b3b84b4 100644 --- a/guides/source/upgrading_ruby_on_rails.md +++ b/guides/source/upgrading_ruby_on_rails.md @@ -1278,6 +1278,10 @@ Also check your environment settings for `config.action_dispatch.best_standards_  Rails 4.0 removes the `j` alias for `ERB::Util#json_escape` since `j` is already used for `ActionView::Helpers::JavaScriptHelper#escape_javascript`. +#### Cache + +The caching method changed between Rails 3.x and 4.0. You should [change the cache namespace](http://guides.rubyonrails.org/caching_with_rails.html#activesupport-cache-store) and roll out with a cold cache. +  ### Helpers Loading Order  The order in which helpers from more than one directory are loaded has changed in Rails 4.0. Previously, they were gathered and then sorted alphabetically. After upgrading to Rails 4.0, helpers will preserve the order of loaded directories and will be sorted alphabetically only within each directory. Unless you explicitly use the `helpers_path` parameter, this change will only impact the way of loading helpers from engines. If you rely on the ordering, you should check if correct methods are available after upgrade. If you would like to change the order in which engines are loaded, you can use `config.railties_order=` method. diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index 8a1a440fca..7aee513a99 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,13 +1,71 @@ +*   Fix running multiple tests in one `rake` command + +    e.g. `bin/rake test:models test:controllers` + +    *Dominic Cleal* + +*   Add option to configure Ruby's warning behaviour to test runner. + +    *Yuji Yaginuma* + +*   Initialize git repo when generating new app, if option `--skip-git` +    is not provided. + +    *Dino Maric* + +*   Install Byebug gem as default in Windows (mingw and x64_mingw) platform. + +    *Junichi Ito* + +*   Make every Rails command work within engines. + +    *Sean Collins*, *Yuji Yaginuma* + +*   Don't generate HTML/ERB templates for scaffold controller with `--api` flag. + +    Fixes #27591. + +    *Prathamesh Sonpatki* + +*   Make `Rails.env` fall back to `development` when `RAILS_ENV` and `RACK_ENV` is an empty string. + +    *Daniel Deng* + +*   Remove deprecated `CONTROLLER` environment variable for `routes` task. + +    *Rafael Mendonça França* + +*   Remove deprecated tasks: `rails:update`, `rails:template`, `rails:template:copy`, +    `rails:update:configs` and `rails:update:bin`. + +    *Rafael Mendonça França* + +*   Remove deprecated file `rails/rack/debugger`. + +    *Rafael Mendonça França* + +*   Remove deprecated `config.serve_static_files`. + +    *Rafael Mendonça França* + +*   Remove deprecated `config.static_cache_control`. + +    *Rafael Mendonça França* + +*   The `log:clear` task clear all environments log files by default. + +    *Yuji Yaginuma* +  *   Add Webpack support in new apps via the --webpack option, which will delegate to the rails/webpacker gem.      To generate a new app that has Webpack dependencies configured and binstubs for webpack and webpack-watcher: -      rails new myapp --webpack +      `rails new myapp --webpack`      To generate a new app that has Webpack + React configured and an example intalled: -     -      rails new myapp --webpack=react -     + +      `rails new myapp --webpack=react` +      *DHH*  *   Add Yarn support in new apps with a yarn binstub and vendor/package.json. Skippable via --skip-yarn option. diff --git a/railties/MIT-LICENSE b/railties/MIT-LICENSE index 1f496cf280..f9e4444f07 100644 --- a/railties/MIT-LICENSE +++ b/railties/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2016 David Heinemeier Hansson +Copyright (c) 2004-2017 David Heinemeier Hansson  Permission is hereby granted, free of charge, to any person obtaining  a copy of this software and associated documentation files (the diff --git a/railties/bin/test b/railties/bin/test new file mode 100755 index 0000000000..a7beb14b27 --- /dev/null +++ b/railties/bin/test @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby + +COMPONENT_ROOT = File.expand_path("..", __dir__) +require File.expand_path("../tools/test", COMPONENT_ROOT) diff --git a/railties/lib/rails.rb b/railties/lib/rails.rb index ee48043a50..00add5829d 100644 --- a/railties/lib/rails.rb +++ b/railties/lib/rails.rb @@ -7,6 +7,7 @@ require "active_support/dependencies/autoload"  require "active_support/core_ext/kernel/reporting"  require "active_support/core_ext/module/delegation"  require "active_support/core_ext/array/extract_options" +require "active_support/core_ext/object/blank"  require "rails/application"  require "rails/version" @@ -67,7 +68,7 @@ module Rails      #   Rails.env.development? # => true      #   Rails.env.production? # => false      def env -      @_env ||= ActiveSupport::StringInquirer.new(ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development") +      @_env ||= ActiveSupport::StringInquirer.new(ENV["RAILS_ENV"].presence || ENV["RACK_ENV"].presence || "development")      end      # Sets the Rails environment. diff --git a/railties/lib/rails/application.rb b/railties/lib/rails/application.rb index f96432c89f..1a6aed7ce4 100644 --- a/railties/lib/rails/application.rb +++ b/railties/lib/rails/application.rb @@ -72,7 +72,7 @@ module Rails    # on one of the applications to create a copy of the application which shares    # the configuration.    # -  # If you decide to define rake tasks, runners, or initializers in an +  # If you decide to define Rake tasks, runners, or initializers in an    # application other than +Rails.application+, then you must run them manually.    class Application < Engine      autoload :Bootstrap,              "rails/application/bootstrap" @@ -265,8 +265,8 @@ module Rails        end      end -    # If you try to define a set of rake tasks on the instance, these will get -    # passed up to the rake tasks defined on the application's class. +    # If you try to define a set of Rake tasks on the instance, these will get +    # passed up to the Rake tasks defined on the application's class.      def rake_tasks(&block)        self.class.rake_tasks(&block)      end @@ -511,7 +511,7 @@ module Rails      def validate_secret_key_config! #:nodoc:        if secrets.secret_key_base.blank? -        ActiveSupport::Deprecation.warn "You didn't set `secret_key_base`. " + +        ActiveSupport::Deprecation.warn "You didn't set `secret_key_base`. " \            "Read the upgrade documentation to learn more about this new config option."          if secrets.secret_token.blank? diff --git a/railties/lib/rails/application/bootstrap.rb b/railties/lib/rails/application/bootstrap.rb index 11da271501..6102af3fff 100644 --- a/railties/lib/rails/application/bootstrap.rb +++ b/railties/lib/rails/application/bootstrap.rb @@ -48,8 +48,8 @@ INFO            logger = ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDERR))            logger.level = ActiveSupport::Logger::WARN            logger.warn( -            "Rails Error: Unable to access log file. Please ensure that #{path} exists and is writable " + -            "(ie, make it writable for user and group: chmod 0664 #{path}). " + +            "Rails Error: Unable to access log file. Please ensure that #{path} exists and is writable " \ +            "(ie, make it writable for user and group: chmod 0664 #{path}). " \              "The log level has been raised to WARN and the output directed to STDERR until the problem is fixed."            )            logger diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index 810750ed35..b0d33f87a3 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -3,9 +3,6 @@ require "active_support/file_update_checker"  require "rails/engine/configuration"  require "rails/source_annotation_extractor" -require "active_support/deprecation" -require "active_support/core_ext/string/strip" # for strip_heredoc -  module Rails    class Application      class Configuration < ::Rails::Engine::Configuration @@ -19,11 +16,11 @@ module Rails                      :beginning_of_week, :filter_redirect, :x, :enable_dependency_loading        attr_writer :log_level -      attr_reader :encoding, :api_only, :static_cache_control +      attr_reader :encoding, :api_only        def initialize(*)          super -        self.encoding = "utf-8" +        self.encoding                    = Encoding::UTF_8          @allow_concurrency               = nil          @consider_all_requests_local     = false          @filter_parameters               = [] @@ -56,35 +53,6 @@ module Rails          @enable_dependency_loading       = false        end -      def static_cache_control=(value) -        ActiveSupport::Deprecation.warn <<-eow.strip_heredoc -          `config.static_cache_control` is deprecated and will be removed in Rails 5.1. -          Please use -          `config.public_file_server.headers = { 'Cache-Control' => '#{value}' }` -          instead. -        eow - -        @static_cache_control = value -      end - -      def serve_static_files -        ActiveSupport::Deprecation.warn <<-eow.strip_heredoc -          `config.serve_static_files` is deprecated and will be removed in Rails 5.1. -          Please use `config.public_file_server.enabled` instead. -        eow - -        @public_file_server.enabled -      end - -      def serve_static_files=(value) -        ActiveSupport::Deprecation.warn <<-eow.strip_heredoc -          `config.serve_static_files` is deprecated and will be removed in Rails 5.1. -          Please use `config.public_file_server.enabled = #{value}` instead. -        eow - -        @public_file_server.enabled = value -      end -        def encoding=(value)          @encoding = value          silence_warnings do @@ -161,7 +129,7 @@ module Rails        def colorize_logging=(val)          ActiveSupport::LogSubscriber.colorize_logging = val -        self.generators.colorize_logging = val +        generators.colorize_logging = val        end        def session_store(new_session_store = nil, **options) diff --git a/railties/lib/rails/application/default_middleware_stack.rb b/railties/lib/rails/application/default_middleware_stack.rb index 14c0a8cbe4..8fe48feefb 100644 --- a/railties/lib/rails/application/default_middleware_stack.rb +++ b/railties/lib/rails/application/default_middleware_stack.rb @@ -19,7 +19,6 @@ module Rails            if config.public_file_server.enabled              headers = config.public_file_server.headers || {} -            headers["Cache-Control".freeze] = config.static_cache_control if config.static_cache_control              middleware.use ::ActionDispatch::Static, paths["public"].first, index: config.public_file_server.index_name, headers: headers            end @@ -41,12 +40,11 @@ module Rails            middleware.use ::Rack::Runtime            middleware.use ::Rack::MethodOverride unless config.api_only            middleware.use ::ActionDispatch::RequestId +          middleware.use ::ActionDispatch::RemoteIp, config.action_dispatch.ip_spoofing_check, config.action_dispatch.trusted_proxies -          # Must come after Rack::MethodOverride to properly log overridden methods            middleware.use ::Rails::Rack::Logger, config.log_tags            middleware.use ::ActionDispatch::ShowExceptions, show_exceptions_app            middleware.use ::ActionDispatch::DebugExceptions, app, config.debug_exception_response_format -          middleware.use ::ActionDispatch::RemoteIp, config.action_dispatch.ip_spoofing_check, config.action_dispatch.trusted_proxies            unless config.cache_classes              middleware.use ::ActionDispatch::Reloader, app.reloader diff --git a/railties/lib/rails/application/finisher.rb b/railties/lib/rails/application/finisher.rb index a855e8fab0..c027d06663 100644 --- a/railties/lib/rails/application/finisher.rb +++ b/railties/lib/rails/application/finisher.rb @@ -124,6 +124,7 @@ module Rails        # the hook are taken into account.        initializer :set_routes_reloader_hook do |app|          reloader = routes_reloader +        reloader.eager_load = app.config.eager_load          reloader.execute_if_updated          reloaders << reloader          app.reloader.to_run do diff --git a/railties/lib/rails/application/routes_reloader.rb b/railties/lib/rails/application/routes_reloader.rb index cf0a4e128f..e02ef629f2 100644 --- a/railties/lib/rails/application/routes_reloader.rb +++ b/railties/lib/rails/application/routes_reloader.rb @@ -4,11 +4,13 @@ module Rails    class Application      class RoutesReloader        attr_reader :route_sets, :paths -      delegate :execute_if_updated, :execute, :updated?, to: :updater +      attr_accessor :eager_load +      delegate :updated?, to: :updater        def initialize          @paths      = []          @route_sets = [] +        @eager_load = false        end        def reload! @@ -19,6 +21,19 @@ module Rails          revert        end +      def execute +        ret = updater.execute +        route_sets.each(&:eager_load!) if eager_load +        ret +      end + +      def execute_if_updated +        if updated = updater.execute_if_updated +          route_sets.each(&:eager_load!) if eager_load +        end +        updated +      end +      private        def updater diff --git a/railties/lib/rails/command.rb b/railties/lib/rails/command.rb index ddb953543f..13f3b90b6d 100644 --- a/railties/lib/rails/command.rb +++ b/railties/lib/rails/command.rb @@ -15,6 +15,8 @@ module Rails      include Behavior +    HELP_MAPPINGS = %w(-h -? --help) +      class << self        def hidden_commands # :nodoc:          @hidden_commands ||= [] @@ -27,7 +29,7 @@ module Rails        # Receives a namespace, arguments and the behavior to invoke the command.        def invoke(namespace, args = [], **config)          namespace = namespace.to_s -        namespace = "help" if namespace.blank? || Thor::HELP_MAPPINGS.include?(namespace) +        namespace = "help" if namespace.blank? || HELP_MAPPINGS.include?(namespace)          namespace = "version" if %w( -v --version ).include? namespace          if command = find_by_namespace(namespace) @@ -37,7 +39,7 @@ module Rails          end        end -      # Rails finds namespaces similar to thor, it only adds one rule: +      # Rails finds namespaces similar to Thor, it only adds one rule:        #        # Command names must end with "_command.rb". This is required because Rails        # looks in load paths and loads the command just before it's going to be used. diff --git a/railties/lib/rails/command/actions.rb b/railties/lib/rails/command/actions.rb index 31b656ec31..fb80e9d997 100644 --- a/railties/lib/rails/command/actions.rb +++ b/railties/lib/rails/command/actions.rb @@ -11,6 +11,11 @@ module Rails        if defined?(ENGINE_PATH)          def require_application_and_environment!            require ENGINE_PATH + +          if defined?(APP_PATH) +            require APP_PATH +            Rails.application.require_environment! +          end          end          def load_tasks diff --git a/railties/lib/rails/command/base.rb b/railties/lib/rails/command/base.rb index 1efcd69e63..1435792536 100644 --- a/railties/lib/rails/command/base.rb +++ b/railties/lib/rails/command/base.rb @@ -22,7 +22,7 @@ module Rails          # Tries to get the description from a USAGE file one folder above the command          # root. -        def desc(usage = nil, description = nil) +        def desc(usage = nil, description = nil, options = {})            if usage              super            else @@ -56,7 +56,7 @@ module Rails          end          def perform(command, args, config) # :nodoc: -          command = nil if Thor::HELP_MAPPINGS.include?(args.first) +          command = nil if Rails::Command::HELP_MAPPINGS.include?(args.first)            dispatch(command, args.dup, nil, config)          end @@ -130,6 +130,14 @@ module Rails              end            end        end + +      def help +        if command_name = self.class.command_name +          self.class.command_help(shell, command_name) +        else +          super +        end +      end      end    end  end diff --git a/railties/lib/rails/commands/help/USAGE b/railties/lib/rails/commands/help/USAGE index 348f41861f..c5f8ab72bb 100644 --- a/railties/lib/rails/commands/help/USAGE +++ b/railties/lib/rails/commands/help/USAGE @@ -1,16 +1,3 @@ -Usage: bin/rails COMMAND [args] [options] -<% if engine? %> -The common Rails commands available for engines are: - generate    Generate new code (short-cut alias: "g") - destroy     Undo code generated with "generate" (short-cut alias: "d") - test        Run tests (short-cut alias: "t") - -All commands can be run with -h for more information. - -If you want to run any commands that need to be run in context -of the application, like `bin/rails server` or `bin/rails console`, -you should do it from the application's directory (typically test/dummy). -<% else %>  The most common rails commands are:   generate    Generate new code (short-cut alias: "g")   console     Start the Rails console (short-cut alias: "c") @@ -18,10 +5,11 @@ The most common rails commands are:   test        Run tests (short-cut alias: "t")   dbconsole   Start a console for the database specified in config/database.yml               (short-cut alias: "db") +<% unless engine? %>   new         Create a new Rails application. "rails new my_app" creates a               new application called MyApp in "./my_app" +<% end %>  All commands can be run with -h (or --help) for more information. -<% end %>  In addition to those commands, there are: diff --git a/railties/lib/rails/commands/rake/rake_command.rb b/railties/lib/rails/commands/rake/rake_command.rb index f03dc81117..075b1fd23d 100644 --- a/railties/lib/rails/commands/rake/rake_command.rb +++ b/railties/lib/rails/commands/rake/rake_command.rb @@ -28,9 +28,7 @@ module Rails              return @rake_tasks if defined?(@rake_tasks) -            ActiveSupport::Deprecation.silence do -              require_application_and_environment! -            end +            require_application_and_environment!              Rake::TaskManager.record_task_metadata = true              Rake.application.instance_variable_set(:@name, "rails") diff --git a/railties/lib/rails/commands/server/server_command.rb b/railties/lib/rails/commands/server/server_command.rb index e9538b804c..d58721f648 100644 --- a/railties/lib/rails/commands/server/server_command.rb +++ b/railties/lib/rails/commands/server/server_command.rb @@ -7,51 +7,14 @@ require "rails/dev_caching"  module Rails    class Server < ::Rack::Server      class Options -      DEFAULT_PID_PATH = File.expand_path("tmp/pids/server.pid").freeze -        def parse!(args) -        args, options = args.dup, {} - -        option_parser(options).parse! args - -        options[:log_stdout] = options[:daemonize].blank? && (options[:environment] || Rails.env) == "development" -        options[:server]     = args.shift -        options -      end - -      def option_parser(options) # :nodoc: -        OptionParser.new do |opts| -          opts.banner = "Usage: rails server [puma, thin etc] [options]" - -          opts.separator "" -          opts.separator "Options:" - -          opts.on("-p", "--port=port", Integer, -                  "Runs Rails on the specified port.", "Default: 3000") { |v| options[:Port] = v } -          opts.on("-b", "--binding=IP", String, -                  "Binds Rails to the specified IP.", "Default: localhost") { |v| options[:Host] = v } -          opts.on("-c", "--config=file", String, -                  "Uses a custom rackup configuration.") { |v| options[:config] = v } -          opts.on("-d", "--daemon", "Runs server as a Daemon.") { options[:daemonize] = true } -          opts.on("-e", "--environment=name", String, -                  "Specifies the environment to run this server under (test/development/production).", -                  "Default: development") { |v| options[:environment] = v } -          opts.on("-P", "--pid=pid", String, -                  "Specifies the PID file.", -                  "Default: tmp/pids/server.pid") { |v| options[:pid] = v } -          opts.on("-C", "--[no-]dev-caching", -                  "Specifies whether to perform caching in development.", -                  "true or false") { |v| options[:caching] = v } - -          opts.separator "" - -          opts.on("-h", "--help", "Shows this help message.") { puts opts; exit } -        end +        Rails::Command::ServerCommand.new([], args).server_options        end      end -    def initialize(*) -      super +    def initialize(options = nil) +      @default_options = options || {} +      super(@default_options)        set_environment      end @@ -90,15 +53,7 @@ module Rails      end      def default_options -      super.merge( -        Port:               ENV.fetch("PORT", 3000).to_i, -        Host:               ENV.fetch("HOST", "localhost").dup, -        DoNotReverseLookup: true, -        environment:        (ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development").dup, -        daemonize:          false, -        caching:            nil, -        pid:                Options::DEFAULT_PID_PATH, -        restart_cmd:        restart_command) +      super.merge(@default_options)      end      private @@ -140,14 +95,33 @@ module Rails    module Command      class ServerCommand < Base # :nodoc: -      def help -        puts Rails::Server::Options.new.option_parser(Hash.new) +      DEFAULT_PID_PATH = "tmp/pids/server.pid".freeze + +      class_option :port, aliases: "-p", type: :numeric, +        desc: "Runs Rails on the specified port.", banner: :port, default: 3000 +      class_option :binding, aliases: "-b", type: :string, default: "localhost", +        desc: "Binds Rails to the specified IP.", banner: :IP +      class_option :config, aliases: "-c", type: :string, default: "config.ru", +        desc: "Uses a custom rackup configuration.", banner: :file +      class_option :daemon, aliases: "-d", type: :boolean, default: false, +        desc: "Runs server as a Daemon." +      class_option :environment, aliases: "-e", type: :string, +        desc: "Specifies the environment to run this server under (development/test/production).", banner: :name +      class_option :pid, aliases: "-P", type: :string, default: DEFAULT_PID_PATH, +        desc: "Specifies the PID file." +      class_option "dev-caching", aliases: "-C", type: :boolean, default: nil, +        desc: "Specifies whether to perform caching in development." + +      def initialize(args = [], local_options = {}, config = {}) +        @original_options = local_options +        super +        @server = self.args.shift +        @log_stdout = options[:daemon].blank? && (options[:environment] || Rails.env) == "development"        end        def perform          set_application_directory! - -        Rails::Server.new.tap do |server| +        Rails::Server.new(server_options).tap do |server|            # Require application after server sets environment to propagate            # the --environment option.            require APP_PATH @@ -155,6 +129,49 @@ module Rails            server.start          end        end + +      no_commands do +        def server_options +          { +            server:             @server, +            log_stdout:         @log_stdout, +            Port:               port, +            Host:               host, +            DoNotReverseLookup: true, +            config:             options[:config], +            environment:        environment, +            daemonize:          options[:daemon], +            pid:                pid, +            caching:            options["dev-caching"], +            restart_cmd:        restart_command +          } +        end +      end + +      private +        def port +          ENV.fetch("PORT", options[:port]).to_i +        end + +        def host +          ENV.fetch("HOST", options[:binding]) +        end + +        def environment +          options[:environment] || Rails::Command.environment +        end + +        def restart_command +          "bin/rails server #{@server} #{@original_options.join(" ")}" +        end + +        def pid +          File.expand_path(options[:pid]) +        end + +        def self.banner(*) +          "rails server [puma, thin etc] [options]" +        end      end    end  end diff --git a/railties/lib/rails/engine.rb b/railties/lib/rails/engine.rb index 2dd1fb3273..13af6051ce 100644 --- a/railties/lib/rails/engine.rb +++ b/railties/lib/rails/engine.rb @@ -109,7 +109,7 @@ module Rails    #    # == Endpoint    # -  # An engine can also be a rack application. It can be useful if you have a rack application that +  # An engine can also be a Rack application. It can be useful if you have a Rack application that    # you would like to wrap with +Engine+ and provide with some of the +Engine+'s features.    #    # To do that, use the +endpoint+ method: @@ -128,7 +128,7 @@ module Rails    #    # == Middleware stack    # -  # As an engine can now be a rack endpoint, it can also have a middleware +  # As an engine can now be a Rack endpoint, it can also have a middleware    # stack. The usage is exactly the same as in <tt>Application</tt>:    #    #   module MyEngine @@ -380,7 +380,7 @@ module Rails        def isolate_namespace(mod)          engine_name(generate_railtie_name(mod.name)) -        self.routes.default_scope = { module: ActiveSupport::Inflector.underscore(mod.name) } +        routes.default_scope = { module: ActiveSupport::Inflector.underscore(mod.name) }          self.isolated = true          unless mod.respond_to?(:railtie_namespace) @@ -499,7 +499,7 @@ module Rails        paths["app/helpers"].existent      end -    # Returns the underlying rack application for this engine. +    # Returns the underlying Rack application for this engine.      def app        @app || @app_build_lock.synchronize {          @app ||= begin @@ -549,7 +549,7 @@ module Rails        load(seed_file) if seed_file      end -    # Add configured load paths to ruby load paths and remove duplicates. +    # Add configured load paths to Ruby's load path, and remove duplicate entries.      initializer :set_load_path, before: :bootstrap_hook do        _all_load_paths.reverse_each do |path|          $LOAD_PATH.unshift(path) if File.directory?(path) @@ -573,7 +573,7 @@ module Rails      end      initializer :add_routing_paths do |app| -      routing_paths = self.paths["config/routes.rb"].existent +      routing_paths = paths["config/routes.rb"].existent        if routes? || routing_paths.any?          app.routes_reloader.paths.unshift(*routing_paths) diff --git a/railties/lib/rails/engine/commands.rb b/railties/lib/rails/engine/commands.rb index a23ae44b0b..b9ef63243a 100644 --- a/railties/lib/rails/engine/commands.rb +++ b/railties/lib/rails/engine/commands.rb @@ -1,12 +1,7 @@ -require "rails/command" +unless defined?(APP_PATH) +  if File.exist?(File.expand_path("test/dummy/config/application.rb", ENGINE_ROOT)) +    APP_PATH = File.expand_path("test/dummy/config/application", ENGINE_ROOT) +  end +end -aliases = { -  "g" => "generate", -  "d" => "destroy", -  "t" => "test" -} - -command = ARGV.shift -command = aliases[command] || command - -Rails::Command.invoke command, ARGV +require "rails/commands" diff --git a/railties/lib/rails/generators.rb b/railties/lib/rails/generators.rb index e1980a42ad..99bda728ee 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -218,7 +218,7 @@ module Rails          [[ "rails", rails ]] + groups.sort.to_a        end -      # Rails finds namespaces similar to thor, it only adds one rule: +      # Rails finds namespaces similar to Thor, it only adds one rule:        #        # Generators names must end with "_generator.rb". This is required because Rails        # looks in load paths and loads the generator just before it's going to be used. diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb index d058d82cea..0bd0615b7e 100644 --- a/railties/lib/rails/generators/actions.rb +++ b/railties/lib/rails/generators/actions.rb @@ -269,7 +269,7 @@ module Rails            if args.size == 1              say args.first.to_s unless options.quiet?            else -            args << (self.behavior == :invoke ? :green : :red) +            args << (behavior == :invoke ? :green : :red)              say_status(*args)            end          end diff --git a/railties/lib/rails/generators/actions/create_migration.rb b/railties/lib/rails/generators/actions/create_migration.rb index f677e545e5..d06609e91e 100644 --- a/railties/lib/rails/generators/actions/create_migration.rb +++ b/railties/lib/rails/generators/actions/create_migration.rb @@ -54,8 +54,8 @@ module Rails                say_status :skip, :yellow              else                say_status :conflict, :red -              raise Error, "Another migration is already named #{migration_file_name}: " + -                "#{existing_migration}. Use --force to replace this migration " + +              raise Error, "Another migration is already named #{migration_file_name}: " \ +                "#{existing_migration}. Use --force to replace this migration " \                  "or --skip to ignore conflicted file."              end            end diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 5c809807a1..ea88afe9f4 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -42,9 +42,6 @@ module Rails          class_option :skip_gemfile,       type: :boolean, default: false,                                            desc: "Don't create a Gemfile" -        class_option :skip_bundle,        type: :boolean, aliases: "-B", default: false, -                                          desc: "Don't run bundle install" -          class_option :skip_git,           type: :boolean, aliases: "-G", default: false,                                            desc: "Skip .gitignore file" @@ -246,6 +243,7 @@ module Rails        def rails_gemfile_entry          dev_edge_common = [ +          GemfileEntry.github("arel", "rails/arel")          ]          if options.dev?            [ diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb index cc6ae3860f..a650c52626 100644 --- a/railties/lib/rails/generators/base.rb +++ b/railties/lib/rails/generators/base.rb @@ -256,8 +256,8 @@ module Rails              last = extract_last_module(nesting)              if last && last.const_defined?(last_name.camelize, false) -              raise Error, "The name '#{class_name}' is either already used in your application " << -                           "or reserved by Ruby on Rails. Please choose an alternative and run "  << +              raise Error, "The name '#{class_name}' is either already used in your application " \ +                           "or reserved by Ruby on Rails. Please choose an alternative and run "  \                             "this generator again."              end            end @@ -331,7 +331,7 @@ module Rails          def self.prepare_for_invocation(name, value) #:nodoc:            return super unless value.is_a?(String) || value.is_a?(Symbol) -          if value && constants = self.hooks[name] +          if value && constants = hooks[name]              value = name if TrueClass === value              Rails::Generators.find_by_namespace(value, *constants)            elsif klass = Rails::Generators.find_by_namespace(value) diff --git a/railties/lib/rails/generators/erb/mailer/mailer_generator.rb b/railties/lib/rails/generators/erb/mailer/mailer_generator.rb index 677f8041ae..3f1d9932f6 100644 --- a/railties/lib/rails/generators/erb/mailer/mailer_generator.rb +++ b/railties/lib/rails/generators/erb/mailer/mailer_generator.rb @@ -9,10 +9,10 @@ module Erb # :nodoc:          view_base_path = File.join("app/views", class_path, file_name + "_mailer")          empty_directory view_base_path -        if self.behavior == :invoke +        if behavior == :invoke            formats.each do |format|              layout_path = File.join("app/views/layouts", class_path, filename_with_extensions("mailer", format)) -            template filename_with_extensions(:layout, format), layout_path +            template filename_with_extensions(:layout, format), layout_path unless File.exist?(layout_path)            end          end diff --git a/railties/lib/rails/generators/migration.rb b/railties/lib/rails/generators/migration.rb index 0d63b9a5c9..82481169c3 100644 --- a/railties/lib/rails/generators/migration.rb +++ b/railties/lib/rails/generators/migration.rb @@ -35,7 +35,7 @@ module Rails        end        def set_migration_assigns!(destination) -        destination = File.expand_path(destination, self.destination_root) +        destination = File.expand_path(destination, destination_root)          migration_dir = File.dirname(destination)          @migration_number     = self.class.next_migration_number(migration_dir) diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb index e3660b012a..6f1925928b 100644 --- a/railties/lib/rails/generators/named_base.rb +++ b/railties/lib/rails/generators/named_base.rb @@ -219,7 +219,7 @@ module Rails          #          def self.check_class_collision(options = {}) # :doc:            define_method :check_class_collision do -            name = if self.respond_to?(:controller_class_name) # for ScaffoldBase +            name = if respond_to?(:controller_class_name) # for ScaffoldBase                controller_class_name              else                class_name diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index 070a6ac124..3cf923faf0 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -53,6 +53,12 @@ module Rails        template "gitignore", ".gitignore"      end +    def version_control +      if !options[:skip_git] && !options[:pretend] +        run "git init" +      end +    end +      def app        directory "app" @@ -175,13 +181,12 @@ module Rails        class_option :api, type: :boolean,                           desc: "Preconfigure smaller stack for API only apps" +      class_option :skip_bundle, type: :boolean, aliases: "-B", default: false, +                                 desc: "Don't run bundle install" +        def initialize(*args)          super -        unless app_path -          raise Error, "Application name should be provided in arguments. For details run: rails --help" -        end -          if !options[:skip_active_record] && !DATABASES.include?(options[:database])            raise Error, "Invalid value for --database option. Supported for preconfiguration are: #{DATABASES.join(", ")}."          end @@ -202,6 +207,7 @@ module Rails          build(:configru)          build(:gitignore)   unless options[:skip_git]          build(:gemfile)     unless options[:skip_gemfile] +        build(:version_control)        end        def create_app_files @@ -320,7 +326,6 @@ module Rails        def delete_action_mailer_files_skipping_action_mailer          if options[:skip_action_mailer] -          remove_file "app/mailers/application_mailer.rb"            remove_file "app/views/layouts/mailer.html.erb"            remove_file "app/views/layouts/mailer.text.erb"            remove_dir "app/mailers" @@ -348,8 +353,8 @@ module Rails          end        end -      def delete_bin_yarn_if_api_option -        remove_file "bin/yarn" if options[:api] +      def delete_bin_yarn_if_skip_yarn_option +        remove_file "bin/yarn" if options[:skip_yarn]        end        def finish_template diff --git a/railties/lib/rails/generators/rails/app/templates/Gemfile b/railties/lib/rails/generators/rails/app/templates/Gemfile index f1015b16d5..24d2fa1284 100644 --- a/railties/lib/rails/generators/rails/app/templates/Gemfile +++ b/railties/lib/rails/generators/rails/app/templates/Gemfile @@ -31,7 +31,7 @@ end  <% if RUBY_ENGINE == 'ruby' -%>  group :development, :test do    # Call 'byebug' anywhere in the code to stop execution and get a debugger console -  gem 'byebug', platform: :mri +  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]  end  group :development do diff --git a/railties/lib/rails/generators/rails/app/templates/config/cable.yml b/railties/lib/rails/generators/rails/app/templates/config/cable.yml index 0bbde6f74f..1da4913082 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/cable.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/cable.yml @@ -7,3 +7,4 @@ test:  production:    adapter: redis    url: redis://localhost:6379/1 +  channel_prefix: <%= app_name %>_production 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 a2b2a64ba6..8bc8735a8e 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 @@ -1,4 +1,4 @@ -# MySQL. Versions 5.0 and up are supported. +# MySQL. Versions 5.1.10 and up are supported.  #  # Install the MySQL driver:  #   gem install activerecord-jdbcmysql-adapter 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 d987cf303b..269af1470d 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 @@ -1,4 +1,4 @@ -# MySQL. Versions 5.0 and up are supported. +# MySQL. Versions 5.1.10 and up are supported.  #  # Install the MySQL driver  #   gem install mysql2 diff --git a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml index c223d6bc62..a21555e573 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/databases/sqlserver.yml @@ -1,4 +1,4 @@ -# SQL Server (2005 or higher recommended) +# SQL Server (2012 or higher recommended)  #  # Install the adapters and driver  #   gem install tiny_tds @@ -8,29 +8,12 @@  #   gem 'tiny_tds'  #   gem 'activerecord-sqlserver-adapter'  # -# You should make sure freetds is configured correctly first. -# freetds.conf contains host/port/protocol_versions settings. -# http://freetds.schemamania.org/userguide/freetdsconf.htm -# -# A typical Microsoft server -#   [mssql] -#   host = mssqlserver.yourdomain.com -#   port = 1433 -#   tds version = 7.1 - -# If you can connect with "tsql -S servername", your basic FreeTDS installation is working. -# 'man tsql' for more info -# Set timeout to a larger number if valid queries against a live db fail -#  default: &default    adapter: sqlserver    encoding: utf8 -  pool: <%%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> -  reconnect: false -  username: <%= app_name %> -  password: -  timeout: 25 -  dataserver: from_freetds.conf +  username: sa +  password: <%= ENV['SA_PASSWORD'] %> +  host: localhost  development:    <<: *default diff --git a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt index 7deab5dbb1..4a39e43e57 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt @@ -88,7 +88,7 @@ Rails.application.configure do    if ENV["RAILS_LOG_TO_STDOUT"].present?      logger           = ActiveSupport::Logger.new(STDOUT)      logger.formatter = config.log_formatter -    config.logger = ActiveSupport::TaggedLogging.new(logger) +    config.logger    = ActiveSupport::TaggedLogging.new(logger)    end    <%- unless options.skip_active_record? -%> diff --git a/railties/lib/rails/generators/rails/app/templates/config/locales/en.yml b/railties/lib/rails/generators/rails/app/templates/config/locales/en.yml index 0653957166..decc5a8573 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/locales/en.yml +++ b/railties/lib/rails/generators/rails/app/templates/config/locales/en.yml @@ -16,6 +16,16 @@  #  # This would use the information in config/locales/es.yml.  # +# The following keys must be escaped otherwise they will not be retrieved by +# the default I18n backend: +# +# true, false, on, off, yes, no +# +# Instead, surround them with single quotes. +# +# en: +#   'true': 'foo' +#  # To learn more, please read the Rails Internationalization guide  # available at http://guides.rubyonrails.org/i18n.html. diff --git a/railties/lib/rails/generators/rails/app/templates/gitignore b/railties/lib/rails/generators/rails/app/templates/gitignore index 709b341387..1768b700d9 100644 --- a/railties/lib/rails/generators/rails/app/templates/gitignore +++ b/railties/lib/rails/generators/rails/app/templates/gitignore @@ -23,6 +23,7 @@  <% unless options[:skip_yarn] -%>  /vendor/node_modules +/vendor/yarn-error.log  <% end -%>  .byebug_history diff --git a/railties/lib/rails/generators/rails/controller/controller_generator.rb b/railties/lib/rails/generators/rails/controller/controller_generator.rb index 01214dc919..06bdb8b5ce 100644 --- a/railties/lib/rails/generators/rails/controller/controller_generator.rb +++ b/railties/lib/rails/generators/rails/controller/controller_generator.rb @@ -16,7 +16,7 @@ module Rails          unless options[:skip_routes]            actions.reverse_each do |action|              # route prepends two spaces onto the front of the string that is passed, this corrects that. -            route generate_routing_code(action) +            route indent(generate_routing_code(action), 2)[2..-1]            end          end        end @@ -34,27 +34,30 @@ module Rails          #   end          # end          def generate_routing_code(action) -          depth = regular_class_path.length +          depth = 0 +          lines = [] +            # Create 'namespace' ladder            # namespace :foo do            #   namespace :bar do -          namespace_ladder = regular_class_path.each_with_index.map do |ns, i| -            indent("  namespace :#{ns} do\n", i * 2) -          end.join[2..-1] +          regular_class_path.each do |ns| +            lines << indent("namespace :#{ns} do\n", depth * 2) +            depth += 1 +          end            # Create route            #     get 'baz/index' -          route = indent(%{  get '#{file_name}/#{action}'\n}, depth * 2) +          lines << indent(%{get '#{file_name}/#{action}'\n}, depth * 2)            # Create `end` ladder            #   end            # end -          end_ladder = (1..depth).reverse_each.map do |i| -            indent("end\n", i * 2) -          end.join +          until depth.zero? +            depth -= 1 +            lines << indent("end\n", depth * 2) +          end -          # Combine the 3 parts to generate complete route entry -          "#{namespace_ladder}#{route}#{end_ladder}" +          lines.join          end      end    end diff --git a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb index 4cf4f8cd9a..49259f32c8 100644 --- a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb +++ b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb @@ -186,7 +186,7 @@ task default: :test                                    desc: "Skip gemspec file"        class_option :skip_gemfile_entry, type: :boolean, default: false, -                                        desc: "If creating plugin in application's directory " + +                                        desc: "If creating plugin in application's directory " \                                                   "skip adding entry to Gemfile"        class_option :api,          type: :boolean, default: false, @@ -195,10 +195,6 @@ task default: :test        def initialize(*args)          @dummy_path = nil          super - -        unless plugin_path -          raise Error, "Plugin name should be provided in arguments. For details run: rails plugin new --help" -        end        end        public_task :set_default_accessors! @@ -415,7 +411,6 @@ task default: :test  require 'rake/testtask'  Rake::TestTask.new(:test) do |t| -  t.libs << 'lib'    t.libs << 'test'    t.pattern = 'test/**/*_test.rb'    t.verbose = false diff --git a/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt b/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt index 56e7925c6b..c03d9953d4 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt +++ b/railties/lib/rails/generators/rails/plugin/templates/bin/rails.tt @@ -3,6 +3,7 @@  ENGINE_ROOT = File.expand_path('../..', __FILE__)  ENGINE_PATH = File.expand_path('../../lib/<%= namespaced_name -%>/engine', __FILE__) +APP_PATH = File.expand_path('../../<%= dummy_path -%>/config/application', __FILE__)  # Set up gems listed in the Gemfile.  ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) diff --git a/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb b/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb index e4f3161ffd..cf97c22160 100644 --- a/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb +++ b/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb @@ -20,7 +20,11 @@ module Rails          template template_file, File.join("app/controllers", controller_class_path, "#{controller_file_name}_controller.rb")        end -      hook_for :template_engine, :test_framework, as: :scaffold +      hook_for :template_engine, as: :scaffold do |template_engine| +        invoke template_engine unless options.api? +      end + +      hook_for :test_framework, as: :scaffold        # Invoke the helper using the controller name (pluralized)        hook_for :helper, as: :scaffold do |invoked| diff --git a/railties/lib/rails/generators/resource_helpers.rb b/railties/lib/rails/generators/resource_helpers.rb index 978b053308..e7cb722473 100644 --- a/railties/lib/rails/generators/resource_helpers.rb +++ b/railties/lib/rails/generators/resource_helpers.rb @@ -59,7 +59,7 @@ module Rails          end          # Loads the ORM::Generators::ActiveModel class. This class is responsible -        # to tell scaffold entities how to generate an specific method for the +        # to tell scaffold entities how to generate a specific method for the          # ORM. Check Rails::Generators::ActiveModel for more information.          def orm_class            @orm_class ||= begin diff --git a/railties/lib/rails/info.rb b/railties/lib/rails/info.rb index 5d4acd6f6b..fc064dac32 100644 --- a/railties/lib/rails/info.rb +++ b/railties/lib/rails/info.rb @@ -1,9 +1,9 @@  require "cgi"  module Rails -  # This module helps build the runtime properties used to display in the -  # Rails::InfoController responses. Including the active Rails version, Ruby -  # version, Rack version, and so on. +  # This module helps build the runtime properties that are displayed in +  # Rails::InfoController responses. These include the active Rails version, +  # Ruby version, Rack version, and so on.    module Info      mattr_accessor :properties      class << (@@properties = []) diff --git a/railties/lib/rails/paths.rb b/railties/lib/rails/paths.rb index 10925de8b2..af3be10a31 100644 --- a/railties/lib/rails/paths.rb +++ b/railties/lib/rails/paths.rb @@ -2,12 +2,12 @@ module Rails    module Paths      # This object is an extended hash that behaves as root of the <tt>Rails::Paths</tt> system.      # It allows you to collect information about how you want to structure your application -    # paths by a Hash like API. It requires you to give a physical path on initialization. +    # paths through a Hash-like API. It requires you to give a physical path on initialization.      #      #   root = Root.new "/rails"      #   root.add "app/controllers", eager_load: true      # -    # The command above creates a new root object and adds "app/controllers" as a path. +    # The above command creates a new root object and adds "app/controllers" as a path.      # This means we can get a <tt>Rails::Paths::Path</tt> object back like below:      #      #   path = root["app/controllers"] diff --git a/railties/lib/rails/rack/debugger.rb b/railties/lib/rails/rack/debugger.rb deleted file mode 100644 index dccfa3e9bb..0000000000 --- a/railties/lib/rails/rack/debugger.rb +++ /dev/null @@ -1,3 +0,0 @@ -require "active_support/deprecation" - -ActiveSupport::Deprecation.warn("This file is deprecated and will be removed in Rails 5.1 with no replacement.") diff --git a/railties/lib/rails/rack/logger.rb b/railties/lib/rails/rack/logger.rb index 0fc202910c..853fc26051 100644 --- a/railties/lib/rails/rack/logger.rb +++ b/railties/lib/rails/rack/logger.rb @@ -29,50 +29,50 @@ module Rails        private -      def call_app(request, env) # :doc: -        instrumenter = ActiveSupport::Notifications.instrumenter -        instrumenter.start "request.action_dispatch", request: request -        logger.info { started_request_message(request) } -        resp = @app.call(env) -        resp[2] = ::Rack::BodyProxy.new(resp[2]) { finish(request) } -        resp -      rescue Exception -        finish(request) -        raise -      ensure -        ActiveSupport::LogSubscriber.flush_all! -      end +        def call_app(request, env) # :doc: +          instrumenter = ActiveSupport::Notifications.instrumenter +          instrumenter.start "request.action_dispatch", request: request +          logger.info { started_request_message(request) } +          resp = @app.call(env) +          resp[2] = ::Rack::BodyProxy.new(resp[2]) { finish(request) } +          resp +        rescue Exception +          finish(request) +          raise +        ensure +          ActiveSupport::LogSubscriber.flush_all! +        end -      # Started GET "/session/new" for 127.0.0.1 at 2012-09-26 14:51:42 -0700 -      def started_request_message(request) # :doc: -        'Started %s "%s" for %s at %s' % [ -          request.request_method, -          request.filtered_path, -          request.ip, -          Time.now.to_default_s ] -      end +        # Started GET "/session/new" for 127.0.0.1 at 2012-09-26 14:51:42 -0700 +        def started_request_message(request) # :doc: +          'Started %s "%s" for %s at %s' % [ +            request.request_method, +            request.filtered_path, +            request.ip, +            Time.now.to_default_s ] +        end -      def compute_tags(request) # :doc: -        @taggers.collect do |tag| -          case tag -          when Proc -            tag.call(request) -          when Symbol -            request.send(tag) -          else -            tag +        def compute_tags(request) # :doc: +          @taggers.collect do |tag| +            case tag +            when Proc +              tag.call(request) +            when Symbol +              request.send(tag) +            else +              tag +            end            end          end -      end -      def finish(request) -        instrumenter = ActiveSupport::Notifications.instrumenter -        instrumenter.finish "request.action_dispatch", request: request -      end +        def finish(request) +          instrumenter = ActiveSupport::Notifications.instrumenter +          instrumenter.finish "request.action_dispatch", request: request +        end -      def logger -        Rails.logger -      end +        def logger +          Rails.logger +        end      end    end  end diff --git a/railties/lib/rails/source_annotation_extractor.rb b/railties/lib/rails/source_annotation_extractor.rb index 3a48c4c496..e9088c44ce 100644 --- a/railties/lib/rails/source_annotation_extractor.rb +++ b/railties/lib/rails/source_annotation_extractor.rb @@ -13,7 +13,7 @@  # start with the tag optionally followed by a colon. Everything up to the end  # of the line (or closing ERB comment tag) is considered to be their text.  class SourceAnnotationExtractor -  class Annotation < Struct.new(:line, :tag, :text) +  Annotation = Struct.new(:line, :tag, :text) do      def self.directories        @@directories ||= %w(app config db lib test) + (ENV["SOURCE_ANNOTATION_DIRECTORIES"] || "").split(",")      end diff --git a/railties/lib/rails/tasks.rb b/railties/lib/rails/tasks.rb index 48675b3845..c0a50e5bda 100644 --- a/railties/lib/rails/tasks.rb +++ b/railties/lib/rails/tasks.rb @@ -12,6 +12,7 @@ require "rake"    restart    routes    tmp +  yarn  ).tap { |arr|    arr << "statistics" if Rake.application.current_scope.empty?  }.each do |task| diff --git a/railties/lib/rails/tasks/framework.rake b/railties/lib/rails/tasks/framework.rake index a2167796eb..f5586b53f0 100644 --- a/railties/lib/rails/tasks/framework.rake +++ b/railties/lib/rails/tasks/framework.rake @@ -1,5 +1,3 @@ -require "active_support/deprecation" -  namespace :app do    desc "Update configs and some other initially generated files (or use just update:configs or update:bin)"    task update: [ "update:configs", "update:bin", "update:upgrade_guide_info" ] @@ -73,15 +71,3 @@ namespace :app do      end    end  end - -namespace :rails do -  %i(update template templates:copy update:configs update:bin).each do |task_name| -    task "#{task_name}" do -      ActiveSupport::Deprecation.warn(<<-MSG.squish) -        Running #{task_name} with the rails: namespace is deprecated in favor of app: namespace. -        Run bin/rails app:#{task_name} instead. -      MSG -      Rake.application.invoke_task("app:#{task_name}") -    end -  end -end diff --git a/railties/lib/rails/tasks/log.rake b/railties/lib/rails/tasks/log.rake index c376234fee..ba796845d7 100644 --- a/railties/lib/rails/tasks/log.rake +++ b/railties/lib/rails/tasks/log.rake @@ -3,7 +3,7 @@ namespace :log do    ##    # Truncates all/specified log files    # ENV['LOGS'] -  #   - defaults to standard environment log files i.e. 'development,test,production' +  #   - defaults to all environments log files i.e. 'development,test,production'    #   - ENV['LOGS']=all truncates all files i.e. log/*.log    #   - ENV['LOGS']='test,development' truncates only specified files    desc "Truncates all/specified *.log files in log/ to zero bytes (specify which logs with LOGS=test,development)" @@ -19,7 +19,7 @@ namespace :log do      elsif ENV["LOGS"]        log_files_to_truncate(ENV["LOGS"])      else -      log_files_to_truncate("development,test,production") +      log_files_to_truncate(all_environments.join(","))      end    end @@ -33,4 +33,8 @@ namespace :log do      f = File.open(file, "w")      f.close    end + +  def all_environments +    Dir["config/environments/*.rb"].map { |fname| File.basename(fname, ".*") } +  end  end diff --git a/railties/lib/rails/tasks/routes.rake b/railties/lib/rails/tasks/routes.rake index f5e5b9ae87..215fb2ceb5 100644 --- a/railties/lib/rails/tasks/routes.rake +++ b/railties/lib/rails/tasks/routes.rake @@ -1,5 +1,3 @@ -require "active_support/deprecation" -require "active_support/core_ext/string/strip" # for strip_heredoc  require "optparse"  desc "Print out all defined routes in match order, with names. Target specific controller with -c option, or grep routes using -g option" @@ -7,15 +5,8 @@ task routes: :environment do    all_routes = Rails.application.routes.routes    require "action_dispatch/routing/inspector"    inspector = ActionDispatch::Routing::RoutesInspector.new(all_routes) -  if ARGV.any? { |argv| argv.start_with? "CONTROLLER" } -    puts <<-eow.strip_heredoc -      Passing `CONTROLLER` to `bin/rails routes` is deprecated and will be removed in Rails 5.1. -      Please use `bin/rails routes -c controller_name` instead. -    eow -  end    routes_filter = nil -  routes_filter = { controller: ENV["CONTROLLER"] } if ENV["CONTROLLER"]    OptionParser.new do |opts|      opts.banner = "Usage: rails routes [options]" diff --git a/railties/lib/rails/tasks/statistics.rake b/railties/lib/rails/tasks/statistics.rake index 8265aef10b..ba1697186e 100644 --- a/railties/lib/rails/tasks/statistics.rake +++ b/railties/lib/rails/tasks/statistics.rake @@ -10,7 +10,6 @@ STATS_DIRECTORIES = [    %w(Channels           app/channels),    %w(JavaScripts        app/assets/javascripts),    %w(Libraries          lib/), -  %w(Tasks              lib/tasks),    %w(APIs               app/apis),    %w(Controller\ tests  test/controllers),    %w(Helper\ tests      test/helpers), diff --git a/railties/lib/rails/tasks/yarn.rake b/railties/lib/rails/tasks/yarn.rake new file mode 100644 index 0000000000..2097b7ffef --- /dev/null +++ b/railties/lib/rails/tasks/yarn.rake @@ -0,0 +1,11 @@ +namespace :yarn do +  desc "Install all JavaScript dependencies as specified via Yarn" +  task :install do +    system('./bin/yarn install --no-progress') +  end +end + +# Run Yarn prior to Sprockets assets precompilation, so dependencies are available for use. +if Rake::Task.task_defined?("assets:precompile") +  Rake::Task["assets:precompile"].enhance [ "yarn:install" ] +end diff --git a/railties/lib/rails/test_help.rb b/railties/lib/rails/test_help.rb index db341dd847..5fda160012 100644 --- a/railties/lib/rails/test_help.rb +++ b/railties/lib/rails/test_help.rb @@ -17,7 +17,7 @@ if defined?(ActiveRecord::Base)    class ActiveSupport::TestCase      include ActiveRecord::TestFixtures      self.fixture_path = "#{Rails.root}/test/fixtures/" -    self.file_fixture_path = self.fixture_path + "files" +    self.file_fixture_path = fixture_path + "files"    end    ActionDispatch::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path diff --git a/railties/lib/rails/test_unit/minitest_plugin.rb b/railties/lib/rails/test_unit/minitest_plugin.rb index 6e196a32ab..4df3e7f0f2 100644 --- a/railties/lib/rails/test_unit/minitest_plugin.rb +++ b/railties/lib/rails/test_unit/minitest_plugin.rb @@ -52,21 +52,25 @@ module Minitest        options[:color] = value      end +    opts.on("-w", "--warnings", +            "Enable ruby warnings") do +      $VERBOSE = true +    end +      options[:color] = true      options[:output_inline] = true -    options[:patterns] = defined?(@rake_patterns) ? @rake_patterns : opts.order! +    options[:patterns] = opts.order! unless run_via[:rake]    end -  # Running several Rake tasks in a single command would trip up the runner, -  # as the patterns would also contain the other Rake tasks.    def self.rake_run(patterns) # :nodoc: -    @rake_patterns = patterns +    run_via[:rake] = true +    ::Rails::TestRequirer.require_files(patterns)      autorun    end    module RunRespectingRakeTestopts      def run(args = []) -      if defined?(@rake_patterns) +      if run_via[:rake]          args = Shellwords.split(ENV["TESTOPTS"] || "")        end @@ -81,8 +85,9 @@ module Minitest    def self.plugin_rails_init(options)      ENV["RAILS_ENV"] = options[:environment] || "test" -    # If run via `ruby` we've been passed the files to run directly. -    unless run_via[:ruby] +    # If run via `ruby` we've been passed the files to run directly, or if run +    # via `rake` then they have already been eagerly required. +    unless run_via[:ruby] || run_via[:rake]        ::Rails::TestRequirer.require_files(options[:patterns])      end diff --git a/railties/test/abstract_unit.rb b/railties/test/abstract_unit.rb index fd1e1b9662..e4b2d0457d 100644 --- a/railties/test/abstract_unit.rb +++ b/railties/test/abstract_unit.rb @@ -17,15 +17,15 @@ module TestApp    end  end -# Skips the current run on Rubinius using Minitest::Assertions#skip -def rubinius_skip(message = "") -  skip message if RUBY_ENGINE == "rbx" -end -# Skips the current run on JRuby using Minitest::Assertions#skip -def jruby_skip(message = "") -  skip message if defined?(JRUBY_VERSION) -end -  class ActiveSupport::TestCase    include ActiveSupport::Testing::Stream + +  # Skips the current run on Rubinius using Minitest::Assertions#skip +  private def rubinius_skip(message = "") +    skip message if RUBY_ENGINE == "rbx" +  end +  # Skips the current run on JRuby using Minitest::Assertions#skip +  private def jruby_skip(message = "") +    skip message if defined?(JRUBY_VERSION) +  end  end diff --git a/railties/test/application/bin_setup_test.rb b/railties/test/application/bin_setup_test.rb index 0bbd25db2b..0fb995900f 100644 --- a/railties/test/application/bin_setup_test.rb +++ b/railties/test/application/bin_setup_test.rb @@ -6,17 +6,10 @@ module ApplicationTests      def setup        build_app - -      create_gemfile -      update_boot_file_to_use_bundler -      @old_gemfile_env = ENV["BUNDLE_GEMFILE"] -      ENV["BUNDLE_GEMFILE"] = app_path + "/Gemfile"      end      def teardown        teardown_app - -      ENV["BUNDLE_GEMFILE"] = @old_gemfile_env      end      def test_bin_setup @@ -45,6 +38,10 @@ module ApplicationTests          app_file "db/schema.rb", ""          output = `bin/setup 2>&1` + +        # Ignore line that's only output by Bundler < 1.14 +        output.sub!(/^Resolving dependencies\.\.\.\n/, "") +          assert_equal(<<-OUTPUT, output)  == Installing dependencies ==  The Gemfile's dependencies are satisfied @@ -59,16 +56,5 @@ Created database 'db/test.sqlite3'          OUTPUT        end      end - -    private -      def create_gemfile -        app_file("Gemfile", "source 'https://rubygems.org'") -        app_file("Gemfile", "gem 'rails', path: '#{RAILS_FRAMEWORK_ROOT}'", "a") -        app_file("Gemfile", "gem 'sqlite3'", "a") -      end - -      def update_boot_file_to_use_bundler -        app_file("config/boot.rb", "require 'bundler/setup'") -      end    end  end diff --git a/railties/test/application/configuration/custom_test.rb b/railties/test/application/configuration/custom_test.rb index 3c675eec71..8360b7bf4b 100644 --- a/railties/test/application/configuration/custom_test.rb +++ b/railties/test/application/configuration/custom_test.rb @@ -10,7 +10,6 @@ module ApplicationTests        def teardown          teardown_app -        FileUtils.rm_rf(new_app) if File.directory?(new_app)        end        test "access custom configuration point" do @@ -30,28 +29,14 @@ module ApplicationTests          assert_equal false, x.hyper_debugger          assert_nil x.nil_debugger          assert_nil x.i_do_not_exist.zomg -      end -      test "custom configuration responds to all messages" do -        x = Rails.configuration.x +        # test that custom configuration responds to all messages          assert_equal true, x.respond_to?(:i_do_not_exist)          assert_kind_of Method, x.method(:i_do_not_exist)          assert_kind_of ActiveSupport::OrderedOptions, x.i_do_not_exist        end        private -        def new_app -          File.expand_path("#{app_path}/../new_app") -        end - -        def copy_app -          FileUtils.cp_r(app_path, new_app) -        end - -        def app -          @app ||= Rails.application -        end -          def require_environment            require "#{app_path}/config/environment"          end diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index 31c3b5cf52..14433fbba0 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -51,7 +51,7 @@ module ApplicationTests      def setup        build_app -      supress_default_config +      suppress_default_config      end      def teardown @@ -59,7 +59,7 @@ module ApplicationTests        FileUtils.rm_rf(new_app) if File.directory?(new_app)      end -    def supress_default_config +    def suppress_default_config        FileUtils.mv("#{app_path}/config/environments", "#{app_path}/config/__environments__")      end @@ -78,6 +78,18 @@ module ApplicationTests        end      end +    test "Rails.env falls back to development if RAILS_ENV is blank and RACK_ENV is nil" do +      with_rails_env("") do +        assert_equal "development", Rails.env +      end +    end + +    test "Rails.env falls back to development if RACK_ENV is blank and RAILS_ENV is nil" do +      with_rack_env("") do +        assert_equal "development", Rails.env +      end +    end +      test "By default logs tags are not set in development" do        restore_default_config @@ -369,26 +381,6 @@ module ApplicationTests        end      end -    test "config.serve_static_files is deprecated" do -      make_basic_app do |application| -        assert_deprecated do -          application.config.serve_static_files = true -        end - -        assert application.config.public_file_server.enabled -      end -    end - -    test "config.static_cache_control is deprecated" do -      make_basic_app do |application| -        assert_deprecated do -          application.config.static_cache_control = "public, max-age=60" -        end - -        assert_equal application.config.static_cache_control, "public, max-age=60" -      end -    end -      test "Use key_generator when secret_key_base is set" do        make_basic_app do |application|          application.secrets.secret_key_base = "b3c631c314c0bbca50c1b2843150fe33" @@ -631,9 +623,10 @@ module ApplicationTests        assert_equal "", app.config.secret_token        assert_nil app.secrets.secret_key_base -      assert_raise ArgumentError, /\AA secret is required/ do +      e = assert_raise ArgumentError do          app.key_generator        end +      assert_match(/\AA secret is required/, e.message)      end      test "that nested keys are symbolized the same as parents for hashes more than one level deep" do @@ -992,7 +985,7 @@ module ApplicationTests        class ::OmgController < ActionController::Base          def index -          render plain: env["action_dispatch.show_exceptions"] +          render plain: request.env["action_dispatch.show_exceptions"]          end        end @@ -1192,11 +1185,12 @@ module ApplicationTests      end      test "config.session_store with :active_record_store without activerecord-session_store gem" do -      assert_raise RuntimeError, /activerecord-session_store/ do +      e = assert_raise RuntimeError do          make_basic_app do |application|            application.config.session_store :active_record_store          end        end +      assert_match(/activerecord-session_store/, e.message)      end      test "default session store initializer does not overwrite the user defined session store even if it is disabled" do diff --git a/railties/test/application/initializers/frameworks_test.rb b/railties/test/application/initializers/frameworks_test.rb index 32bce7d372..90927159dd 100644 --- a/railties/test/application/initializers/frameworks_test.rb +++ b/railties/test/application/initializers/frameworks_test.rb @@ -219,7 +219,7 @@ module ApplicationTests        end        require "#{app_path}/config/environment"        ActiveRecord::Base.connection.drop_table("posts") # force drop posts table for test. -      assert ActiveRecord::Base.connection.schema_cache.tables("posts") +      assert ActiveRecord::Base.connection.schema_cache.data_sources("posts")      end      test "expire schema cache dump" do @@ -227,10 +227,8 @@ module ApplicationTests          `rails generate model post title:string;           bin/rails db:migrate db:schema:cache:dump db:rollback`        end -      silence_warnings { -        require "#{app_path}/config/environment" -        assert !ActiveRecord::Base.connection.schema_cache.tables("posts") -      } +      require "#{app_path}/config/environment" +      assert !ActiveRecord::Base.connection.schema_cache.data_sources("posts")      end      test "active record establish_connection uses Rails.env if DATABASE_URL is not set" do diff --git a/railties/test/application/mailer_previews_test.rb b/railties/test/application/mailer_previews_test.rb index 790aca2aa4..c3a360e5d4 100644 --- a/railties/test/application/mailer_previews_test.rb +++ b/railties/test/application/mailer_previews_test.rb @@ -671,6 +671,40 @@ module ApplicationTests        assert_match %r[<p>Hello, World!</p>], last_response.body      end +    test "multipart mailer preview with empty parts" do +      mailer "notifier", <<-RUBY +        class Notifier < ActionMailer::Base +          default from: "from@example.com" + +          def foo +            mail to: "to@example.org" +          end +        end +      RUBY + +      text_template "notifier/foo", <<-RUBY +      RUBY + +      html_template "notifier/foo", <<-RUBY +      RUBY + +      mailer_preview "notifier", <<-RUBY +        class NotifierPreview < ActionMailer::Preview +          def foo +            Notifier.foo +          end +        end +      RUBY + +      app("development") + +      get "/rails/mailers/notifier/foo?part=text/plain" +      assert_equal 200, last_response.status + +      get "/rails/mailers/notifier/foo?part=text/html" +      assert_equal 200, last_response.status +    end +      private        def build_app          super diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb index be41dcb299..0a6e5b52e9 100644 --- a/railties/test/application/middleware_test.rb +++ b/railties/test/application/middleware_test.rb @@ -30,10 +30,10 @@ module ApplicationTests          "Rack::Runtime",          "Rack::MethodOverride",          "ActionDispatch::RequestId", -        "Rails::Rack::Logger", # must come after Rack::MethodOverride to properly log overridden methods +        "ActionDispatch::RemoteIp", +        "Rails::Rack::Logger",          "ActionDispatch::ShowExceptions",          "ActionDispatch::DebugExceptions", -        "ActionDispatch::RemoteIp",          "ActionDispatch::Reloader",          "ActionDispatch::Callbacks",          "ActiveRecord::Migration::CheckPending", @@ -58,10 +58,10 @@ module ApplicationTests          "ActiveSupport::Cache::Strategy::LocalCache",          "Rack::Runtime",          "ActionDispatch::RequestId", -        "Rails::Rack::Logger", # must come after Rack::MethodOverride to properly log overridden methods +        "ActionDispatch::RemoteIp", +        "Rails::Rack::Logger",          "ActionDispatch::ShowExceptions",          "ActionDispatch::DebugExceptions", -        "ActionDispatch::RemoteIp",          "ActionDispatch::Reloader",          "ActionDispatch::Callbacks",          "Rack::Head", @@ -70,6 +70,37 @@ module ApplicationTests        ], middleware      end +    test "middleware dependencies" do +      boot! + +      # The following array-of-arrays describes dependencies between +      # middlewares: the first item in each list depends on the +      # remaining items (and therefore must occur later in the +      # middleware stack). + +      dependencies = [ +        # Logger needs a fully "corrected" request environment +        %w(Rails::Rack::Logger Rack::MethodOverride ActionDispatch::RequestId ActionDispatch::RemoteIp), + +        # Serving public/ doesn't invoke user code, so it should skip +        # locks etc +        %w(ActionDispatch::Executor ActionDispatch::Static), + +        # Errors during reload must be reported +        %w(ActionDispatch::Reloader ActionDispatch::ShowExceptions ActionDispatch::DebugExceptions), + +        # Outright dependencies +        %w(ActionDispatch::Static Rack::Sendfile), +        %w(ActionDispatch::Flash ActionDispatch::Session::CookieStore), +        %w(ActionDispatch::Session::CookieStore ActionDispatch::Cookies), +      ] + +      require "tsort" +      sorted = TSort.tsort((middleware | dependencies.flatten).method(:each), +                           lambda { |n, &b| dependencies.each { |m, *ds| ds.each(&b) if m == n } }) +      assert_equal sorted, middleware +    end +      test "Rack::Cache is not included by default" do        boot! diff --git a/railties/test/application/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb index 51db634b75..8bbae64d5e 100644 --- a/railties/test/application/rake/dbs_test.rb +++ b/railties/test/application/rake/dbs_test.rb @@ -175,7 +175,7 @@ module ApplicationTests            `bin/rails generate model book title:string;             bin/rails db:migrate db:structure:dump`            structure_dump = File.read("db/structure.sql") -          assert_match(/CREATE TABLE \"books\"/, structure_dump) +          assert_match(/CREATE TABLE (?:IF NOT EXISTS )?\"books\"/, structure_dump)            `bin/rails environment db:drop db:structure:load`            assert_match expected_database, ActiveRecord::Base.connection_config[:database]            require "#{app_path}/app/models/book" @@ -203,7 +203,7 @@ module ApplicationTests            stderr_output = capture(:stderr) { `bin/rails db:structure:dump` }            assert_empty stderr_output            structure_dump = File.read("db/structure.sql") -          assert_match(/CREATE TABLE \"posts\"/, structure_dump) +          assert_match(/CREATE TABLE (?:IF NOT EXISTS )?\"posts\"/, structure_dump)          end        end diff --git a/railties/test/application/rake/log_test.rb b/railties/test/application/rake/log_test.rb new file mode 100644 index 0000000000..fdd3c71fe8 --- /dev/null +++ b/railties/test/application/rake/log_test.rb @@ -0,0 +1,33 @@ +require "isolation/abstract_unit" + +module ApplicationTests +  module RakeTests +    class LogTest < ActiveSupport::TestCase +      include ActiveSupport::Testing::Isolation + +      def setup +        build_app +      end + +      def teardown +        teardown_app +      end + +      test "log:clear clear all environments log files by default" do +        Dir.chdir(app_path) do +          File.open("config/environments/staging.rb", "w") + +          File.write("log/staging.log", "staging") +          File.write("log/test.log", "test") +          File.write("log/dummy.log", "dummy") + +          `rails log:clear` + +          assert_equal 0, File.size("log/test.log") +          assert_equal 0, File.size("log/staging.log") +          assert_equal 5, File.size("log/dummy.log") +        end +      end +    end +  end +end diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb index d80a45a83f..1b64a0a1ca 100644 --- a/railties/test/application/rake_test.rb +++ b/railties/test/application/rake_test.rb @@ -132,48 +132,6 @@ module ApplicationTests        assert_equal "Prefix Verb URI Pattern     Controller#Action\n  cart GET  /cart(.:format) cart#show\n", output      end -    def test_rails_routes_with_controller_environment -      app_file "config/routes.rb", <<-RUBY -        Rails.application.routes.draw do -          get '/cart', to: 'cart#show' -          get '/basketball', to: 'basketball#index' -        end -      RUBY - -      output = Dir.chdir(app_path) { `bin/rails routes CONTROLLER=cart` } -      assert_equal ["Passing `CONTROLLER` to `bin/rails routes` is deprecated and will be removed in Rails 5.1.", -                    "Please use `bin/rails routes -c controller_name` instead.", -                    "Prefix Verb URI Pattern     Controller#Action", -                    "  cart GET  /cart(.:format) cart#show\n"].join("\n"), output - -      output = Dir.chdir(app_path) { `bin/rails routes -c cart` } -      assert_equal "Prefix Verb URI Pattern     Controller#Action\n  cart GET  /cart(.:format) cart#show\n", output -    end - -    def test_rails_routes_with_namespaced_controller_environment -      app_file "config/routes.rb", <<-RUBY -        Rails.application.routes.draw do -          namespace :admin do -            resource :post -          end -        end -      RUBY -      expected_output = ["         Prefix Verb   URI Pattern                Controller#Action", -                         " new_admin_post GET    /admin/post/new(.:format)  admin/posts#new", -                         "edit_admin_post GET    /admin/post/edit(.:format) admin/posts#edit", -                         "     admin_post GET    /admin/post(.:format)      admin/posts#show", -                         "                PATCH  /admin/post(.:format)      admin/posts#update", -                         "                PUT    /admin/post(.:format)      admin/posts#update", -                         "                DELETE /admin/post(.:format)      admin/posts#destroy", -                         "                POST   /admin/post(.:format)      admin/posts#create\n"].join("\n") - -      output = Dir.chdir(app_path) { `bin/rails routes -c Admin::PostController` } -      assert_equal expected_output, output - -      output = Dir.chdir(app_path) { `bin/rails routes -c PostController` } -      assert_equal expected_output, output -    end -      def test_singular_resource_output_in_rake_routes        app_file "config/routes.rb", <<-RUBY          Rails.application.routes.draw do @@ -232,6 +190,30 @@ module ApplicationTests        assert_equal "Prefix Verb URI Pattern     Controller#Action\n  cart GET  /cart(.:format) cart#show\n", output      end +    def test_rails_routes_with_namespaced_controller_search_key +      app_file "config/routes.rb", <<-RUBY +        Rails.application.routes.draw do +          namespace :admin do +            resource :post +          end +        end +      RUBY +      expected_output = ["         Prefix Verb   URI Pattern                Controller#Action", +                         " new_admin_post GET    /admin/post/new(.:format)  admin/posts#new", +                         "edit_admin_post GET    /admin/post/edit(.:format) admin/posts#edit", +                         "     admin_post GET    /admin/post(.:format)      admin/posts#show", +                         "                PATCH  /admin/post(.:format)      admin/posts#update", +                         "                PUT    /admin/post(.:format)      admin/posts#update", +                         "                DELETE /admin/post(.:format)      admin/posts#destroy", +                         "                POST   /admin/post(.:format)      admin/posts#create\n"].join("\n") + +      output = Dir.chdir(app_path) { `bin/rails routes -c Admin::PostController` } +      assert_equal expected_output, output + +      output = Dir.chdir(app_path) { `bin/rails routes -c PostController` } +      assert_equal expected_output, output +    end +      def test_rails_routes_displays_message_when_no_routes_are_defined        app_file "config/routes.rb", <<-RUBY          Rails.application.routes.draw do @@ -341,16 +323,6 @@ module ApplicationTests        assert_no_match(/Errors running/, output)      end -    def test_db_test_clone_when_using_sql_format -      add_to_config "config.active_record.schema_format = :sql" -      output = Dir.chdir(app_path) do -        `bin/rails generate scaffold user username:string; -         bin/rails db:migrate; -         bin/rails db:test:clone 2>&1 --trace` -      end -      assert_match(/Execute db:test:clone_structure/, output) -    end -      def test_db_test_prepare_when_using_sql_format        add_to_config "config.active_record.schema_format = :sql"        output = Dir.chdir(app_path) do diff --git a/railties/test/application/test_runner_test.rb b/railties/test/application/test_runner_test.rb index 0939587960..d3d5b6d6dd 100644 --- a/railties/test/application/test_runner_test.rb +++ b/railties/test/application/test_runner_test.rb @@ -536,6 +536,32 @@ module ApplicationTests        assert_match "seed=1234", output, "passing TEST= should run selected test"      end +    def test_rake_runs_multiple_test_tasks +      create_test_file :models, "account" +      create_test_file :controllers, "accounts_controller" +      output = Dir.chdir(app_path) { `bin/rake test:models test:controllers TESTOPTS='-v'` } +      assert_match "AccountTest#test_truth", output +      assert_match "AccountsControllerTest#test_truth", output +    end + +    def test_rake_db_and_test_tasks_parses_args_correctly +      create_test_file :models, "account" +      output = Dir.chdir(app_path) { `bin/rake db:migrate test:models TESTOPTS='-v' && echo ".tables" | rails dbconsole` } +      assert_match "AccountTest#test_truth", output +      assert_match "ar_internal_metadata", output +    end + +    def test_warnings_option +      app_file "test/models/warnings_test.rb", <<-RUBY +        require 'test_helper' +        def test_warnings +          a = 1 +        end +      RUBY +      assert_match(/warning: assigned but unused variable/, +        capture(:stderr) { run_test_command("test/models/warnings_test.rb -w") }) +    end +      private        def run_test_command(arguments = "test/unit/test_test.rb")          Dir.chdir(app_path) { `bin/rails t #{arguments}` } diff --git a/railties/test/commands/server_test.rb b/railties/test/commands/server_test.rb index 2e8076ed2b..e3dfc3e82b 100644 --- a/railties/test/commands/server_test.rb +++ b/railties/test/commands/server_test.rb @@ -7,31 +7,35 @@ class Rails::ServerTest < ActiveSupport::TestCase    include EnvHelpers    def test_environment_with_server_option -    args    = ["thin", "-e", "production"] -    options = Rails::Server::Options.new.parse!(args) +    args = ["thin", "-e", "production"] +    options = parse_arguments(args)      assert_equal "production", options[:environment]      assert_equal "thin", options[:server]    end    def test_environment_without_server_option -    args    = ["-e", "production"] -    options = Rails::Server::Options.new.parse!(args) +    args = ["-e", "production"] +    options = parse_arguments(args)      assert_equal "production", options[:environment]      assert_nil options[:server]    end    def test_server_option_without_environment -    args    = ["thin"] -    options = Rails::Server::Options.new.parse!(args) -    assert_nil options[:environment] -    assert_equal "thin", options[:server] +    args = ["thin"] +    with_rack_env nil do +      with_rails_env nil do +        options = parse_arguments(args) +        assert_equal "development",  options[:environment] +        assert_equal "thin", options[:server] +      end +    end    end    def test_environment_with_rails_env      with_rack_env nil do        with_rails_env "production" do -        server = Rails::Server.new -        assert_equal "production", server.options[:environment] +        options = parse_arguments +        assert_equal "production", options[:environment]        end      end    end @@ -39,40 +43,39 @@ class Rails::ServerTest < ActiveSupport::TestCase    def test_environment_with_rack_env      with_rails_env nil do        with_rack_env "production" do -        server = Rails::Server.new -        assert_equal "production", server.options[:environment] +        options = parse_arguments +        assert_equal "production", options[:environment]        end      end    end    def test_environment_with_port      switch_env "PORT", "1234" do -      server = Rails::Server.new -      assert_equal 1234, server.options[:Port] +      options = parse_arguments +      assert_equal 1234, options[:Port]      end    end    def test_environment_with_host      switch_env "HOST", "1.2.3.4" do -      server = Rails::Server.new -      assert_equal "1.2.3.4", server.options[:Host] +      options = parse_arguments +      assert_equal "1.2.3.4", options[:Host]      end    end    def test_caching_without_option      args = [] -    options = Rails::Server::Options.new.parse!(args) -    merged_options = Rails::Server.new.default_options.merge(options) -    assert_nil merged_options[:caching] +    options = parse_arguments(args) +    assert_nil options[:caching]    end    def test_caching_with_option      args = ["--dev-caching"] -    options = Rails::Server::Options.new.parse!(args) +    options = parse_arguments(args)      assert_equal true, options[:caching]      args = ["--no-dev-caching"] -    options = Rails::Server::Options.new.parse!(args) +    options = parse_arguments(args)      assert_equal false, options[:caching]    end @@ -80,38 +83,38 @@ class Rails::ServerTest < ActiveSupport::TestCase      with_rack_env nil do        with_rails_env nil do          args    = [] -        options = Rails::Server::Options.new.parse!(args) +        options = parse_arguments(args)          assert_equal true, options[:log_stdout]          args    = ["-e", "development"] -        options = Rails::Server::Options.new.parse!(args) +        options = parse_arguments(args)          assert_equal true, options[:log_stdout]          args    = ["-e", "production"] -        options = Rails::Server::Options.new.parse!(args) +        options = parse_arguments(args)          assert_equal false, options[:log_stdout]          with_rack_env "development" do            args    = [] -          options = Rails::Server::Options.new.parse!(args) +          options = parse_arguments(args)            assert_equal true, options[:log_stdout]          end          with_rack_env "production" do            args    = [] -          options = Rails::Server::Options.new.parse!(args) +          options = parse_arguments(args)            assert_equal false, options[:log_stdout]          end          with_rails_env "development" do            args    = [] -          options = Rails::Server::Options.new.parse!(args) +          options = parse_arguments(args)            assert_equal true, options[:log_stdout]          end          with_rails_env "production" do            args    = [] -          options = Rails::Server::Options.new.parse!(args) +          options = parse_arguments(args)            assert_equal false, options[:log_stdout]          end        end @@ -129,15 +132,19 @@ class Rails::ServerTest < ActiveSupport::TestCase    def test_restart_command_contains_customized_options      original_args = ARGV.dup -    args = ["-p", "4567"] +    args = %w(-p 4567 -b 127.0.0.1 -c dummy_config.ru -d -e test -P tmp/server.pid -C)      ARGV.replace args -    options = Rails::Server::Options.new.parse! args -    server = Rails::Server.new options -    expected = "bin/rails server -p 4567" +    options = parse_arguments(args) +    expected = "bin/rails server  -p 4567 -b 127.0.0.1 -c dummy_config.ru -d -e test -P tmp/server.pid -C" -    assert_equal expected, server.default_options[:restart_cmd] +    assert_equal expected, options[:restart_cmd]    ensure      ARGV.replace original_args    end + +  private +    def parse_arguments(args = []) +      Rails::Command::ServerCommand.new([], args).server_options +    end  end diff --git a/railties/test/engine/commands_tasks_test.rb b/railties/test/engine/commands_tasks_test.rb deleted file mode 100644 index 817175b9ef..0000000000 --- a/railties/test/engine/commands_tasks_test.rb +++ /dev/null @@ -1,24 +0,0 @@ -require "abstract_unit" - -class Rails::Engine::CommandsTasksTest < ActiveSupport::TestCase -  def setup -    @destination_root = Dir.mktmpdir("bukkits") -    Dir.chdir(@destination_root) { `bundle exec rails plugin new bukkits --mountable` } -  end - -  def teardown -    FileUtils.rm_rf(@destination_root) -  end - -  def test_help_command_work_inside_engine -    output = capture(:stderr) do -      Dir.chdir(plugin_path) { `bin/rails --help` } -    end -    assert_no_match "NameError", output -  end - -  private -    def plugin_path -      "#{@destination_root}/bukkits" -    end -end diff --git a/railties/test/engine/commands_test.rb b/railties/test/engine/commands_test.rb new file mode 100644 index 0000000000..b1c937143f --- /dev/null +++ b/railties/test/engine/commands_test.rb @@ -0,0 +1,96 @@ +require "abstract_unit" +begin +  require "pty" +rescue LoadError +end + +class Rails::Engine::CommandsTest < ActiveSupport::TestCase +  def setup +    @destination_root = Dir.mktmpdir("bukkits") +    Dir.chdir(@destination_root) { `bundle exec rails plugin new bukkits --mountable` } +  end + +  def teardown +    FileUtils.rm_rf(@destination_root) +  end + +  def test_help_command_work_inside_engine +    output = capture(:stderr) do +      Dir.chdir(plugin_path) { `bin/rails --help` } +    end +    assert_no_match "NameError", output +  end + +  def test_runner_command_work_inside_engine +    output = capture(:stdout) do +      Dir.chdir(plugin_path) { system("bin/rails runner 'puts Rails.env'") } +    end + +    assert_equal "test", output.strip +  end + +  def test_console_command_work_inside_engine +    skip "PTY unavailable" unless available_pty? + +    master, slave = PTY.open +    spawn_command("console", slave) +    assert_output(">", master) +  ensure +    master.puts "quit" +  end + +  def test_dbconsole_command_work_inside_engine +    skip "PTY unavailable" unless available_pty? + +    master, slave = PTY.open +    spawn_command("dbconsole", slave) +    assert_output("sqlite>", master) +  ensure +    master.puts ".exit" +  end + +  def test_server_command_work_inside_engine +    skip "PTY unavailable" unless available_pty? + +    master, slave = PTY.open +    pid = spawn_command("server", slave) +    assert_output("Listening on", master) +  ensure +    kill(pid) +  end + +  private +    def plugin_path +      "#{@destination_root}/bukkits" +    end + +    def assert_output(expected, io, timeout = 10) +      timeout = Time.now + timeout + +      output = "" +      until output.include?(expected) || Time.now > timeout +        if IO.select([io], [], [], 0.1) +          output << io.read(1) +        end +      end + +      assert_includes output, expected, "#{expected.inspect} expected, but got:\n\n#{output}" +    end + +    def spawn_command(command, fd) +      Process.spawn( +        "#{plugin_path}/bin/rails #{command}", +        in: fd, out: fd, err: fd +      ) +    end + +    def available_pty? +      defined?(PTY) && PTY.respond_to?(:open) +    end + +    def kill(pid) +      Process.kill("TERM", pid) +      Process.wait(pid) +    rescue Errno::ESRCH +    end +end diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 079ee3765c..e80c253ec9 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -419,6 +419,13 @@ class AppGeneratorTest < Rails::Generators::TestCase      end    end +  def test_generator_if_skip_yarn_is_given +    run_generator [destination_root, "--skip-yarn"] + +    assert_no_file "vendor/package.json" +    assert_no_file "bin/yarn" +  end +    def test_generator_if_skip_action_cable_is_given      run_generator [destination_root, "--skip-action-cable"]      assert_file "config/application.rb", /#\s+require\s+["']action_cable\/engine["']/ @@ -501,6 +508,11 @@ class AppGeneratorTest < Rails::Generators::TestCase      assert_file "config/initializers/assets.rb" do |content|        assert_no_match(/node_modules/, content)      end + +    assert_file ".gitignore" do |content| +      assert_no_match(/vendor\/node_modules/, content) +      assert_no_match(/vendor\/yarn-error\.log/, content) +    end    end    def test_inclusion_of_jbuilder @@ -585,6 +597,7 @@ class AppGeneratorTest < Rails::Generators::TestCase    def test_pretend_option      output = run_generator [File.join(destination_root, "myapp"), "--pretend"]      assert_no_match(/run  bundle install/, output) +    assert_no_match(/run  git init/, output)    end    def test_application_name_with_spaces @@ -724,6 +737,11 @@ class AppGeneratorTest < Rails::Generators::TestCase      end    end +  def test_version_control_initializes_git_repo +    run_generator [destination_root] +    assert_directory ".git" +  end +    def test_create_keeps      run_generator      folders_with_keep = %w( @@ -770,7 +788,7 @@ class AppGeneratorTest < Rails::Generators::TestCase        template      end -    sequence = ["install", "exec spring binstub --all", "echo ran after_bundle"] +    sequence = ["git init", "install", "exec spring binstub --all", "echo ran after_bundle"]      @sequence_step ||= 0      ensure_bundler_first = -> command do        assert_equal sequence[@sequence_step], command, "commands should be called in sequence #{sequence}" @@ -785,7 +803,7 @@ class AppGeneratorTest < Rails::Generators::TestCase        end      end -    assert_equal 3, @sequence_step +    assert_equal 4, @sequence_step    end    private diff --git a/railties/test/generators/controller_generator_test.rb b/railties/test/generators/controller_generator_test.rb index 9b986a636a..af86a0136f 100644 --- a/railties/test/generators/controller_generator_test.rb +++ b/railties/test/generators/controller_generator_test.rb @@ -65,7 +65,7 @@ class ControllerGeneratorTest < Rails::Generators::TestCase    def test_add_routes      run_generator -    assert_file "config/routes.rb", /get 'account\/foo'/, /get 'account\/bar'/ +    assert_file "config/routes.rb", /^  get 'account\/foo'/, /^  get 'account\/bar'/    end    def test_skip_routes diff --git a/railties/test/generators/migration_generator_test.rb b/railties/test/generators/migration_generator_test.rb index 6e1d1b70a9..f46278cefe 100644 --- a/railties/test/generators/migration_generator_test.rb +++ b/railties/test/generators/migration_generator_test.rb @@ -309,6 +309,17 @@ class MigrationGeneratorTest < Rails::Generators::TestCase      end    end +  def test_add_migration_to_configured_path +    old_paths = Rails.application.config.paths["db/migrate"] +    Rails.application.config.paths.add "db/migrate", with: "db2/migrate" + +    migration = "migration_in_custom_path" +    run_generator [migration] +    assert_migration "db2/migrate/#{migration}.rb", /.*/ +  ensure +    Rails.application.config.paths["db/migrate"] = old_paths +  end +    private      def with_singular_table_name diff --git a/railties/test/generators/model_generator_test.rb b/railties/test/generators/model_generator_test.rb index 2b9f3ed7f2..99490af3a9 100644 --- a/railties/test/generators/model_generator_test.rb +++ b/railties/test/generators/model_generator_test.rb @@ -220,6 +220,17 @@ class ModelGeneratorTest < Rails::Generators::TestCase      ActiveRecord::Base.timestamped_migrations = true    end +  def test_migration_with_configured_path +    old_paths = Rails.application.config.paths["db/migrate"] +    Rails.application.config.paths.add "db/migrate", with: "db2/migrate" + +    run_generator + +    assert_migration "db2/migrate/create_accounts.rb", /class CreateAccounts < ActiveRecord::Migration\[[0-9.]+\]/ +  ensure +    Rails.application.config.paths["db/migrate"] = old_paths +  end +    def test_model_with_references_attribute_generates_belongs_to_associations      run_generator ["product", "name:string", "supplier:references"]      assert_file "app/models/product.rb", /belongs_to :supplier/ diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb index 9921a80342..ddfbc1a698 100644 --- a/railties/test/generators/plugin_generator_test.rb +++ b/railties/test/generators/plugin_generator_test.rb @@ -421,6 +421,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase      run_generator [destination_root, "--full"]      assert_file "bin/rails", /ENGINE_PATH = File.expand_path\('..\/..\/lib\/bukkits\/engine', __FILE__\)/      assert_file "bin/rails", /ENGINE_ROOT = File.expand_path\('..\/..', __FILE__\)/ +    assert_file "bin/rails", %r|APP_PATH = File.expand_path\('../../test/dummy/config/application', __FILE__\)|      assert_file "bin/rails", /require 'rails\/all'/      assert_file "bin/rails", /require 'rails\/engine\/commands'/    end diff --git a/railties/test/generators/plugin_test_runner_test.rb b/railties/test/generators/plugin_test_runner_test.rb index 0bdf3b2726..0bdd2a77d2 100644 --- a/railties/test/generators/plugin_test_runner_test.rb +++ b/railties/test/generators/plugin_test_runner_test.rb @@ -92,6 +92,17 @@ class PluginTestRunnerTest < ActiveSupport::TestCase      assert_equal 1, result.scan(/1 runs, 1 assertions, 0 failures/).length    end +  def test_warnings_option +    plugin_file "test/models/warnings_test.rb", <<-RUBY +      require 'test_helper' +      def test_warnings +        a = 1 +      end +    RUBY +    assert_match(/warning: assigned but unused variable/, +      capture(:stderr) { run_test_command("test/models/warnings_test.rb -w") }) +  end +    private      def plugin_path        "#{@destination_root}/bukkits" diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb index bd23faf268..9971626f9f 100644 --- a/railties/test/generators/scaffold_controller_generator_test.rb +++ b/railties/test/generators/scaffold_controller_generator_test.rb @@ -230,6 +230,12 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase          assert_match(/@user\.destroy/, m)        end      end + +    assert_no_file "app/views/users/index.html.erb" +    assert_no_file "app/views/users/edit.html.erb" +    assert_no_file "app/views/users/show.html.erb" +    assert_no_file "app/views/users/new.html.erb" +    assert_no_file "app/views/users/_form.html.erb"    end    def test_api_controller_tests diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb index 08b0e34fe2..cc9d3629e9 100644 --- a/railties/test/generators/shared_generator_tests.rb +++ b/railties/test/generators/shared_generator_tests.rb @@ -109,6 +109,7 @@ module SharedGeneratorTests    def test_skip_git      run_generator [destination_root, "--skip-git", "--full"]      assert_no_file(".gitignore") +    assert_no_directory(".git")    end    def test_skip_keeps diff --git a/railties/test/rack_logger_test.rb b/railties/test/rack_logger_test.rb index 7dd91a2465..33b4bc6a3a 100644 --- a/railties/test/rack_logger_test.rb +++ b/railties/test/rack_logger_test.rb @@ -20,7 +20,7 @@ module Rails          def development?; false; end        end -      class Subscriber < Struct.new(:starts, :finishes) +      Subscriber = Struct.new(:starts, :finishes) do          def initialize(starts = [], finishes = [])            super          end diff --git a/railties/test/railties/engine_test.rb b/railties/test/railties/engine_test.rb index 70a6cd90b2..52d691b73b 100644 --- a/railties/test/railties/engine_test.rb +++ b/railties/test/railties/engine_test.rb @@ -890,7 +890,7 @@ YAML        boot_rails -      assert_equal AppTemplate.railtie_namespace, AppTemplate::Engine +      assert_equal AppTemplate::Engine, AppTemplate.railtie_namespace      end      test "properly reload routes" do  | 
