diff options
823 files changed, 10098 insertions, 6515 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 e1102b45ce..0d1d0c36ce 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -6,6 +6,7 @@ AllCops: Exclude: - '**/templates/**/*' - '**/vendor/**/*' + - 'actionpack/lib/action_dispatch/journey/parser.rb' # Prefer &&/|| over and/or. Style/AndOr: @@ -32,6 +33,10 @@ Style/EmptyLines: Style/EmptyLinesAroundClassBody: Enabled: true +# In a regular method definition, no empty lines around the body. +Style/EmptyLinesAroundMethodBody: + Enabled: true + # In a regular module definition, no empty lines around the body. Style/EmptyLinesAroundModuleBody: Enabled: true @@ -112,7 +117,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 86328e0ef6..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" @@ -34,19 +35,19 @@ env: - "JRUBY_OPTS='--dev -J-Xmx1024M'" matrix: - "GEM=railties" - - "GEM=ap" - - "GEM=ac" - - "GEM=ac:integration" + - "GEM=ap,ac" - "GEM=am,amo,as,av,aj" - "GEM=as PRESERVE_TIMEZONES=1" - "GEM=ar:mysql2" - "GEM=ar:sqlite3" - "GEM=ar:postgresql" - "GEM=guides" + - "GEM=ac:integration" rvm: - 2.2.6 - 2.3.3 + - 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,21 @@ 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 notifications: @@ -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,22 +31,19 @@ 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.0.1" + gem "kindlerb", "~> 1.2.0" end # Active Support. @@ -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", 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 eb14330c2b..c40730a33d 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,29 +24,17 @@ GIT websocket GIT - remote: https://github.com/resque/resque.git - revision: 20d885065ac19e7f7d7a982f4ed1296083db0300 + remote: https://github.com/rails/arel.git + revision: ab109d3bf1c773da5e78ddc93bb6b55aebbb1c2a specs: - resque (1.27.0) - mono_logger (~> 1.0) - multi_json (~> 1.0) - redis-namespace (~> 1.3) - 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) + arel (8.0.0) 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 +52,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 +63,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 +93,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 +104,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 +113,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) @@ -162,14 +133,19 @@ GEM coffee-script (2.4.1) coffee-script-source execjs - coffee-script-source (1.10.0) - concurrent-ruby (1.0.2) - connection_pool (2.2.0) + coffee-script-source (1.12.2) + 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 +157,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,20 +177,20 @@ 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) + ffi (1.9.17-x64-mingw32) + ffi (1.9.17-x86-mingw32) 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) + i18n (0.8.0) + jquery-rails (4.2.2) rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) thor (>= 0.14, < 2.0) - json (2.0.2) - kindlerb (1.0.1) + json (2.0.3) + kindlerb (1.2.0) mustache nokogiri libxml-ruby (2.9.0) @@ -241,22 +218,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.4.0.0) 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,74 +249,81 @@ 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) - rake (11.3.0) - rb-fsevent (0.9.7) - rdoc (5.0.0.beta2) + rainbow (2.2.1) + rake (12.0.0) + 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 (1.27.0) + mono_logger (~> 1.0) + multi_json (~> 1.0) + redis-namespace (~> 1.3) + sinatra (>= 0.9.2) + vegas (~> 0.1.2) resque-scheduler (4.3.0) mono_logger (~> 1.0) 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.0) + 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 +331,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 +340,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 +366,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,13 +375,14 @@ 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) - kindlerb (>= 1.0.1) + kindlerb (~> 1.2.0) libxml-ruby listen (>= 3.0.5, < 3.2) minitest (< 5.3.4) @@ -416,12 +402,11 @@ DEPENDENCIES rb-inotify! redcarpet (~> 3.2.3) redis - resque! + 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 +421,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 8ad9aeb1f1..c55b7dc57b 100644 --- a/actioncable/README.md +++ b/actioncable/README.md @@ -51,7 +51,7 @@ module ApplicationCable self.current_user = find_verified_user end - protected + private def find_verified_user if current_user = User.find_by(id: cookies.signed[:user_id]) current_user @@ -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/channel/base.rb b/actioncable/lib/action_cable/channel/base.rb index a866044f95..718f630f58 100644 --- a/actioncable/lib/action_cable/channel/base.rb +++ b/actioncable/lib/action_cable/channel/base.rb @@ -122,16 +122,16 @@ module ActionCable end end - protected + private # action_methods are cached and there is sometimes need to refresh # them. ::clear_action_methods! allows you to do that, so next time # you run action_methods, they will be recalculated. - def clear_action_methods! + def clear_action_methods! # :doc: @action_methods = nil end # Refresh the cached action_methods when a new action_method is added. - def method_added(name) + def method_added(name) # :doc: super clear_action_methods! end @@ -189,23 +189,23 @@ module ActionCable end end - protected + private # Called once a consumer has become a subscriber of the channel. Usually the place to setup any streams # you want this channel to be sending to the subscriber. - def subscribed + def subscribed # :doc: # Override in subclasses end # Called once a consumer has cut its cable connection. Can be used for cleaning up connections or marking # users as offline or the like. - def unsubscribed + def unsubscribed # :doc: # Override in subclasses end # Transmit a hash of data to the subscriber. The hash will automatically be wrapped in a JSON envelope with # the proper channel identifier marked as the recipient. - def transmit(data, via: nil) - logger.info "#{self.class.name} transmitting #{data.inspect.truncate(300)}".tap { |m| m << " (via #{via})" if via } + def transmit(data, via: nil) # :doc: + logger.debug "#{self.class.name} transmitting #{data.inspect.truncate(300)}".tap { |m| m << " (via #{via})" if via } payload = { channel_class: self.class.name, data: data, via: via } ActiveSupport::Notifications.instrument("transmit.action_cable", payload) do @@ -213,33 +213,32 @@ module ActionCable end end - def ensure_confirmation_sent + def ensure_confirmation_sent # :doc: return if subscription_rejected? @defer_subscription_confirmation_counter.decrement transmit_subscription_confirmation unless defer_subscription_confirmation? end - def defer_subscription_confirmation! + def defer_subscription_confirmation! # :doc: @defer_subscription_confirmation_counter.increment end - def defer_subscription_confirmation? + def defer_subscription_confirmation? # :doc: @defer_subscription_confirmation_counter.value > 0 end - def subscription_confirmation_sent? + def subscription_confirmation_sent? # :doc: @subscription_confirmation_sent end - def reject + def reject # :doc: @reject_subscription = true end - def subscription_rejected? + def subscription_rejected? # :doc: @reject_subscription end - private def delegate_connection_identifiers connection.identifiers.each do |identifier| define_singleton_method(identifier) do diff --git a/actioncable/lib/action_cable/connection/base.rb b/actioncable/lib/action_cable/connection/base.rb index dfee123ea2..0a517a532d 100644 --- a/actioncable/lib/action_cable/connection/base.rb +++ b/actioncable/lib/action_cable/connection/base.rb @@ -22,13 +22,10 @@ module ActionCable # # Any cleanup work needed when the cable connection is cut. # end # - # protected + # private # def find_verified_user - # if current_user = User.find_by_identity cookies.signed[:identity_id] - # current_user - # else + # User.find_by_identity(cookies.signed[:identity_id]) || # reject_unauthorized_connection - # end # end # end # end @@ -136,9 +133,15 @@ module ActionCable send_async :handle_close 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 :websocket + attr_reader :message_buffer + + private # The request that initiated the WebSocket connection is available here. This gives access to the environment, cookies, etc. - def request + def request # :doc: @request ||= begin environment = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application ActionDispatch::Request.new(environment || env) @@ -146,14 +149,10 @@ module ActionCable end # The cookies of the request that initiated the WebSocket connection. Useful for performing authorization checks. - def cookies + def cookies # :doc: request.cookie_jar end - attr_reader :websocket - attr_reader :message_buffer - - private def encode(cable_message) @coder.encode cable_message end 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/message_buffer.rb b/actioncable/lib/action_cable/connection/message_buffer.rb index 6a80770cae..4ccd322644 100644 --- a/actioncable/lib/action_cable/connection/message_buffer.rb +++ b/actioncable/lib/action_cable/connection/message_buffer.rb @@ -28,6 +28,8 @@ module ActionCable receive_buffered_messages 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 :connection attr_reader :buffered_messages diff --git a/actioncable/lib/action_cable/connection/subscriptions.rb b/actioncable/lib/action_cable/connection/subscriptions.rb index 00511aead5..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) @@ -61,6 +61,8 @@ module ActionCable subscriptions.each { |id, channel| remove_subscription(channel) } 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 :connection, :subscriptions diff --git a/actioncable/lib/action_cable/connection/tagged_logger_proxy.rb b/actioncable/lib/action_cable/connection/tagged_logger_proxy.rb index 41afa9680a..aef549aa86 100644 --- a/actioncable/lib/action_cable/connection/tagged_logger_proxy.rb +++ b/actioncable/lib/action_cable/connection/tagged_logger_proxy.rb @@ -31,8 +31,8 @@ module ActionCable end end - protected - def log(type, message) + private + def log(type, message) # :doc: tag(@logger) { @logger.send type, message } end end diff --git a/actioncable/lib/action_cable/connection/web_socket.rb b/actioncable/lib/action_cable/connection/web_socket.rb index 382141b89f..03eb6e2ea8 100644 --- a/actioncable/lib/action_cable/connection/web_socket.rb +++ b/actioncable/lib/action_cable/connection/web_socket.rb @@ -32,6 +32,8 @@ module ActionCable websocket.rack_response 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 :websocket end 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/server/broadcasting.rb b/actioncable/lib/action_cable/server/broadcasting.rb index 1fc58baa3e..7fcd6c6587 100644 --- a/actioncable/lib/action_cable/server/broadcasting.rb +++ b/actioncable/lib/action_cable/server/broadcasting.rb @@ -38,7 +38,7 @@ module ActionCable end def broadcast(message) - server.logger.info "[ActionCable] Broadcasting to #{broadcasting}: #{message.inspect}" + server.logger.debug "[ActionCable] Broadcasting to #{broadcasting}: #{message.inspect}" payload = { broadcasting: broadcasting, message: message, coder: coder } ActiveSupport::Notifications.instrument("broadcast.action_cable", payload) do 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/USAGE b/actioncable/lib/rails/generators/channel/USAGE index 6249553c22..dd109fda80 100644 --- a/actioncable/lib/rails/generators/channel/USAGE +++ b/actioncable/lib/rails/generators/channel/USAGE @@ -3,7 +3,7 @@ Description: Stubs out a new cable channel for the server (in Ruby) and client (in CoffeeScript). Pass the channel name, either CamelCased or under_scored, and an optional list of channel actions as arguments. - Note: Turn on the cable connection in app/assets/javascript/cable.js after generating any channels. + Note: Turn on the cable connection in app/assets/javascripts/cable.js after generating any channels. Example: ======== @@ -11,4 +11,4 @@ Example: creates a Chat channel class and CoffeeScript asset: Channel: app/channels/chat_channel.rb - Assets: app/assets/javascript/channels/chat.coffee + Assets: app/assets/javascripts/channels/chat.coffee diff --git a/actioncable/lib/rails/generators/channel/channel_generator.rb b/actioncable/lib/rails/generators/channel/channel_generator.rb index 20d807c033..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 @@ -23,14 +23,14 @@ module Rails generate_application_cable_files end - protected + private def file_name @_file_name ||= super.gsub(/_channel/i, "") end # 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/connection/identifier_test.rb b/actioncable/test/connection/identifier_test.rb index a4dfcc06f0..f3d3bc0603 100644 --- a/actioncable/test/connection/identifier_test.rb +++ b/actioncable/test/connection/identifier_test.rb @@ -53,7 +53,7 @@ class ActionCable::Connection::IdentifierTest < ActionCable::TestCase end end - protected + private def open_connection_with_stubbed_pubsub server = TestServer.new server.stubs(:adapter).returns(stub_everything("adapter")) diff --git a/actioncable/test/connection/multiple_identifiers_test.rb b/actioncable/test/connection/multiple_identifiers_test.rb index 67e68355c5..ca1a08f4d6 100644 --- a/actioncable/test/connection/multiple_identifiers_test.rb +++ b/actioncable/test/connection/multiple_identifiers_test.rb @@ -19,7 +19,7 @@ class ActionCable::Connection::MultipleIdentifiersTest < ActionCable::TestCase end end - protected + private def open_connection_with_stubbed_pubsub server = TestServer.new server.stubs(:pubsub).returns(stub_everything("pubsub")) diff --git a/actioncable/test/connection/string_identifier_test.rb b/actioncable/test/connection/string_identifier_test.rb index 87484765e5..6d53e249cb 100644 --- a/actioncable/test/connection/string_identifier_test.rb +++ b/actioncable/test/connection/string_identifier_test.rb @@ -21,7 +21,7 @@ class ActionCable::Connection::StringIdentifierTest < ActionCable::TestCase end end - protected + private def open_connection_with_stubbed_pubsub @server = TestServer.new @server.stubs(:pubsub).returns(stub_everything("pubsub")) 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 1f5738bbab..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! @@ -544,9 +556,9 @@ module ActionMailer end end - protected + private - def set_payload_for_mail(payload, mail) #:nodoc: + def set_payload_for_mail(payload, mail) payload[:mailer] = name payload[:message_id] = mail.message_id payload[:subject] = mail.subject @@ -558,7 +570,7 @@ module ActionMailer payload[:mail] = mail.encoded end - def method_missing(method_name, *args) # :nodoc: + def method_missing(method_name, *args) if action_methods.include?(method_name.to_s) MessageDelivery.new(self, method_name, *args) else @@ -566,10 +578,8 @@ module ActionMailer end end - private - - def respond_to_missing?(method, include_all = false) #:nodoc: - action_methods.include?(method.to_s) + def respond_to_missing?(method, include_all = false) + action_methods.include?(method.to_s) || super end end @@ -588,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 @@ -830,7 +841,7 @@ module ActionMailer message end - protected + private # Used by #mail to set the content type of the message. # @@ -841,7 +852,7 @@ module ActionMailer # If there is no content type passed in via headers, and there are no # attachments, or the message is multipart, then the default content type is # used. - def set_content_type(m, user_content_type, class_default) + def set_content_type(m, user_content_type, class_default) # :doc: params = m.content_type_parameters || {} case when user_content_type.present? @@ -863,23 +874,21 @@ module ActionMailer # If it does not find a translation for the +subject+ under the specified scope it will default to a # humanized version of the <tt>action_name</tt>. # If the subject has interpolations, you can pass them through the +interpolations+ parameter. - def default_i18n_subject(interpolations = {}) + def default_i18n_subject(interpolations = {}) # :doc: mailer_scope = self.class.mailer_name.tr("/", ".") I18n.t(:subject, interpolations.merge(scope: [mailer_scope, action_name], default: action_name.humanize)) end # Emails do not support relative path links. - def self.supports_path? + def self.supports_path? # :doc: false end - private - def apply_defaults(headers) 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 @@ -900,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 93a11453bf..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 @@ -94,22 +94,22 @@ module ActionMailer name.sub(/Preview$/, "").underscore end - protected - def load_previews #:nodoc: + private + def load_previews if preview_path Dir["#{preview_path}/**/*_preview.rb"].each { |file| require_dependency file } end end - def preview_path #:nodoc: + def preview_path Base.preview_path end - def show_previews #:nodoc: + def show_previews Base.show_previews end - def inform_preview_interceptors(message) #:nodoc: + def inform_preview_interceptors(message) Base.preview_interceptors.each do |interceptor| interceptor.previewing_email(message) end diff --git a/actionmailer/lib/action_mailer/test_case.rb b/actionmailer/lib/action_mailer/test_case.rb index 1d09a4ee96..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) @@ -73,38 +73,36 @@ module ActionMailer end end - protected + private - def initialize_test_deliveries # :nodoc: + def initialize_test_deliveries set_delivery_method :test @old_perform_deliveries = ActionMailer::Base.perform_deliveries ActionMailer::Base.perform_deliveries = true ActionMailer::Base.deliveries.clear end - def restore_test_deliveries # :nodoc: + def restore_test_deliveries restore_delivery_method ActionMailer::Base.perform_deliveries = @old_perform_deliveries end - def set_delivery_method(method) # :nodoc: + def set_delivery_method(method) @old_delivery_method = ActionMailer::Base.delivery_method ActionMailer::Base.delivery_method = method end - def restore_delivery_method # :nodoc: + def restore_delivery_method ActionMailer::Base.deliveries.clear ActionMailer::Base.delivery_method = @old_delivery_method end - def set_expected_mail # :nodoc: + def set_expected_mail @expected = Mail.new @expected.content_type ["text", "plain", { "charset" => charset }] @expected.mime_version = "1.0" end - private - def charset "UTF-8" end 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 9dd7ee7a27..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 @@ -19,12 +19,11 @@ module Rails hook_for :template_engine, :test_framework - protected - def file_name + private + def file_name # :doc: @_file_name ||= super.gsub(/_mailer/i, "") end - private def application_mailer_file_name @_application_mailer_file_name ||= if mountable_engine? "app/mailers/#{namespaced_path}/application_mailer.rb" 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 3bca69890d..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,7 +834,26 @@ class BaseTest < ActiveSupport::TestCase assert_equal "special indeed!", mail["X-Special-Header"].to_s end - protected + 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 # the block is executed. diff --git a/actionmailer/test/i18n_with_controller_test.rb b/actionmailer/test/i18n_with_controller_test.rb index 039685ffe5..4f09339800 100644 --- a/actionmailer/test/i18n_with_controller_test.rb +++ b/actionmailer/test/i18n_with_controller_test.rb @@ -57,16 +57,14 @@ 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 end end - protected + private def with_translation(locale, data) I18n.backend.store_translations(locale, data) diff --git a/actionmailer/test/mail_helper_test.rb b/actionmailer/test/mail_helper_test.rb index 972629e477..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| @@ -65,7 +65,7 @@ The second end end - protected + private def mail_with_defaults(&block) mail(to: "test@localhost", from: "tester@example.com", 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/caching.rb b/actionpack/lib/abstract_controller/caching.rb index d222880922..26e3f08bc1 100644 --- a/actionpack/lib/abstract_controller/caching.rb +++ b/actionpack/lib/abstract_controller/caching.rb @@ -52,9 +52,9 @@ module AbstractController self.class._view_cache_dependencies.map { |dep| instance_exec(&dep) }.compact end - protected + private # Convenience accessor. - def cache(key, options = {}, &block) + def cache(key, options = {}, &block) # :doc: if cache_configured? cache_store.fetch(ActiveSupport::Cache.expand_cache_key(key, :controller), options, &block) else diff --git a/actionpack/lib/abstract_controller/collector.rb b/actionpack/lib/abstract_controller/collector.rb index 57714b0588..40ae5aa1ca 100644 --- a/actionpack/lib/abstract_controller/collector.rb +++ b/actionpack/lib/abstract_controller/collector.rb @@ -19,7 +19,7 @@ module AbstractController generate_method_for_mime(mime) unless instance_methods.include?(mime.to_sym) end - protected + private def method_missing(symbol, &block) unless mime_constant = Mime[symbol] 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 ed93a2f09c..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. @@ -139,8 +134,8 @@ module ActionController end end - def self.encoding_for_param(action, param) # :nodoc: - ::Encoding::UTF_8 + def self.binary_params_for?(action) # :nodoc: + false end # Delegates to the class' <tt>controller_name</tt> @@ -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/data_streaming.rb b/actionpack/lib/action_controller/metal/data_streaming.rb index f089c8423b..731e03e2fc 100644 --- a/actionpack/lib/action_controller/metal/data_streaming.rb +++ b/actionpack/lib/action_controller/metal/data_streaming.rb @@ -11,7 +11,7 @@ module ActionController #:nodoc: DEFAULT_SEND_FILE_TYPE = "application/octet-stream".freeze #:nodoc: DEFAULT_SEND_FILE_DISPOSITION = "attachment".freeze #:nodoc: - protected + private # Sends the file. This uses a server-appropriate method (such as X-Sendfile) # via the Rack::Sendfile middleware. The header to use is set via # +config.action_dispatch.x_sendfile_header+. @@ -70,7 +70,6 @@ module ActionController #:nodoc: send_file_headers! options self.status = options[:status] || 200 - self.content_type = options[:type] if options.key?(:type) self.content_type = options[:content_type] if options.key?(:content_type) response.send_file path end @@ -109,10 +108,12 @@ module ActionController #:nodoc: render options.slice(:status, :content_type).merge(body: data) end - private def send_file_headers!(options) type_provided = options.has_key?(:type) + self.content_type = DEFAULT_SEND_FILE_TYPE + response.sending_file = true + content_type = options.fetch(:type, DEFAULT_SEND_FILE_TYPE) raise ArgumentError, ":type option required" if content_type.nil? @@ -137,8 +138,6 @@ module ActionController #:nodoc: headers["Content-Transfer-Encoding"] = "binary" - response.sending_file = true - # Fix a problem with IE 6.0 on opening downloaded files: # If Cache-Control: no-cache is set (which Rails does by default), # IE removes the file it just downloaded from its cache immediately diff --git a/actionpack/lib/action_controller/metal/flash.rb b/actionpack/lib/action_controller/metal/flash.rb index 65351284b9..347fbf0e74 100644 --- a/actionpack/lib/action_controller/metal/flash.rb +++ b/actionpack/lib/action_controller/metal/flash.rb @@ -42,7 +42,7 @@ module ActionController #:nodoc: end end - protected + private def redirect_to(options = {}, response_status_and_flash = {}) #:doc: self.class._flash_types.each do |flash_type| if type = response_status_and_flash.delete(flash_type) 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/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb index 5bf0a99fe4..0575360068 100644 --- a/actionpack/lib/action_controller/metal/http_authentication.rb +++ b/actionpack/lib/action_controller/metal/http_authentication.rb @@ -28,7 +28,7 @@ module ActionController # class ApplicationController < ActionController::Base # before_action :set_account, :authenticate # - # protected + # private # def set_account # @account = Account.find_by(url_name: request.subdomains.first) # end @@ -363,7 +363,7 @@ module ActionController # class ApplicationController < ActionController::Base # before_action :set_account, :authenticate # - # protected + # private # def set_account # @account = Account.find_by(url_name: request.subdomains.first) # end diff --git a/actionpack/lib/action_controller/metal/implicit_render.rb b/actionpack/lib/action_controller/metal/implicit_render.rb index 8615c16c6f..dde924e682 100644 --- a/actionpack/lib/action_controller/metal/implicit_render.rb +++ b/actionpack/lib/action_controller/metal/implicit_render.rb @@ -1,5 +1,3 @@ -require "active_support/core_ext/string/strip" - module ActionController # Handles implicit rendering for a controller action that does not # explicitly respond with +render+, +respond_to+, +redirect+, or +head+. diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb index f83396ae55..924686218f 100644 --- a/actionpack/lib/action_controller/metal/instrumentation.rb +++ b/actionpack/lib/action_controller/metal/instrumentation.rb @@ -83,14 +83,14 @@ module ActionController # end # # :api: plugin - def cleanup_view_runtime #:nodoc: + def cleanup_view_runtime yield end # Every time after an action is processed, this method is invoked # with the payload, so you can add more information. # :api: plugin - def append_info_to_payload(payload) #:nodoc: + def append_info_to_payload(payload) payload[:view_runtime] = view_runtime end diff --git a/actionpack/lib/action_controller/metal/parameter_encoding.rb b/actionpack/lib/action_controller/metal/parameter_encoding.rb index c457fd0d06..962532ff09 100644 --- a/actionpack/lib/action_controller/metal/parameter_encoding.rb +++ b/actionpack/lib/action_controller/metal/parameter_encoding.rb @@ -1,5 +1,5 @@ module ActionController - # Allows encoding to be specified per parameter per action. + # Specify binary encoding for parameters for a given action. module ParameterEncoding extend ActiveSupport::Concern @@ -13,17 +13,36 @@ module ActionController @_parameter_encodings = {} end - def encoding_for_param(action, param) # :nodoc: - if @_parameter_encodings[action.to_s] && @_parameter_encodings[action.to_s][param.to_s] - @_parameter_encodings[action.to_s][param.to_s] - else - super - end + def binary_params_for?(action) # :nodoc: + @_parameter_encodings[action.to_s] end - def parameter_encoding(action, param_name, encoding) - @_parameter_encodings[action.to_s] ||= {} - @_parameter_encodings[action.to_s][param_name.to_s] = encoding + # Specify that a given action's parameters should all be encoded as + # ASCII-8BIT (it "skips" the encoding default of UTF-8). + # + # For example, a controller would use it like this: + # + # class RepositoryController < ActionController::Base + # skip_parameter_encoding :show + # + # def show + # @repo = Repository.find_by_filesystem_path params[:file_path] + # + # # `repo_name` is guaranteed to be UTF-8, but was ASCII-8BIT, so + # # tag it as such + # @repo_name = params[:repo_name].force_encoding 'UTF-8' + # end + # + # def index + # @repositories = Repository.all + # end + # end + # + # The show action in the above controller would have all parameter values + # encoded as ASCII-8BIT. This is useful in the case where an application + # must handle data but encoding of the data is unknown, like file system data. + def skip_parameter_encoding(action) + @_parameter_encodings[action.to_s] = true end end end diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb index 86e817fe16..7fc898f034 100644 --- a/actionpack/lib/action_controller/metal/params_wrapper.rb +++ b/actionpack/lib/action_controller/metal/params_wrapper.rb @@ -135,7 +135,7 @@ module ActionController # # This method also does namespace lookup. Foo::Bar::UsersController will # try to find Foo::Bar::User, Foo::User and finally User. - def _default_wrap_model #:nodoc: + def _default_wrap_model return nil if klass.anonymous? model_name = klass.name.sub(/Controller$/, "").classify 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 e971917ca2..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) @@ -67,20 +73,20 @@ module ActionController end def _set_rendered_content_type(format) - unless response.content_type + if format && !response.content_type self.content_type = format.to_s end end # Normalize arguments by catching blocks and setting them on :update. - def _normalize_args(action = nil, options = {}, &blk) #:nodoc: + def _normalize_args(action = nil, options = {}, &blk) options = super options[:update] = blk if block_given? options end # Normalize both text and status options. - def _normalize_options(options) #:nodoc: + def _normalize_options(options) _normalize_text(options) if options[:html] @@ -103,12 +109,12 @@ module ActionController end # Process controller specific options, as status, content-type and location. - def _process_options(options) #:nodoc: + def _process_options(options) status, content_type, location = options.values_at(:status, :content_type, :location) 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/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index 3d3c121280..e8965a6561 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -152,7 +152,7 @@ module ActionController #:nodoc: request.cookie_jar = NullCookieJar.build(request, {}) end - protected + private class NullSessionHash < Rack::Session::Abstract::SessionHash #:nodoc: def initialize(req) @@ -197,7 +197,7 @@ module ActionController #:nodoc: end end - protected + private # The actual before_action that is used to verify the CSRF token. # Don't override this directly. Provide your own forgery protection # strategy instead. If you override, you'll disable same-origin @@ -208,7 +208,7 @@ module ActionController #:nodoc: # enabled on an action, this before_action flags its after_action to # verify that JavaScript responses are for XHR requests, ensuring they # follow the browser's same-origin policy. - def verify_authenticity_token + def verify_authenticity_token # :doc: mark_for_same_origin_verification! if !verified_request? @@ -219,7 +219,7 @@ module ActionController #:nodoc: end end - def handle_unverified_request + def handle_unverified_request # :doc: forgery_protection_strategy.new(self).handle_unverified_request end @@ -233,7 +233,7 @@ module ActionController #:nodoc: # If `verify_authenticity_token` was run (indicating that we have # forgery protection enabled for this request) then also verify that # we aren't serving an unauthorized cross-origin response. - def verify_same_origin_request + def verify_same_origin_request # :doc: if marked_for_same_origin_verification? && non_xhr_javascript_response? if logger && log_warning_on_csrf_failure logger.warn CROSS_ORIGIN_JAVASCRIPT_WARNING @@ -243,18 +243,18 @@ module ActionController #:nodoc: end # GET requests are checked for cross-origin JavaScript after rendering. - def mark_for_same_origin_verification! + def mark_for_same_origin_verification! # :doc: @marked_for_same_origin_verification = request.get? end # If the `verify_authenticity_token` before_action ran, verify that # JavaScript responses are only served to same-origin GET requests. - def marked_for_same_origin_verification? + def marked_for_same_origin_verification? # :doc: @marked_for_same_origin_verification ||= false end # Check for cross-origin JavaScript responses. - def non_xhr_javascript_response? + def non_xhr_javascript_response? # :doc: content_type =~ %r(\Atext/javascript) && !request.xhr? end @@ -265,20 +265,20 @@ module ActionController #:nodoc: # * Is it a GET or HEAD request? Gets should be safe and idempotent # * Does the form_authenticity_token match the given token value from the params? # * Does the X-CSRF-Token header match the form_authenticity_token - def verified_request? + def verified_request? # :doc: !protect_against_forgery? || request.get? || request.head? || (valid_request_origin? && any_authenticity_token_valid?) end # Checks if any of the authenticity tokens from the request are valid. - def any_authenticity_token_valid? + def any_authenticity_token_valid? # :doc: request_authenticity_tokens.any? do |token| valid_authenticity_token?(session, token) end end # Possible authenticity tokens sent in the request. - def request_authenticity_tokens + def request_authenticity_tokens # :doc: [form_authenticity_param, request.x_csrf_token] end @@ -290,7 +290,7 @@ module ActionController #:nodoc: # Creates a masked version of the authenticity token that varies # on each request. The masking is used to mitigate SSL attacks # like BREACH. - def masked_authenticity_token(session, form_options: {}) + def masked_authenticity_token(session, form_options: {}) # :doc: action, method = form_options.values_at(:action, :method) raw_token = if per_form_csrf_tokens && action && method @@ -309,7 +309,7 @@ module ActionController #:nodoc: # Checks the client's masked token to see if it matches the # session token. Essentially the inverse of # +masked_authenticity_token+. - def valid_authenticity_token?(session, encoded_masked_token) + def valid_authenticity_token?(session, encoded_masked_token) # :doc: if encoded_masked_token.nil? || encoded_masked_token.empty? || !encoded_masked_token.is_a?(String) return false end @@ -340,7 +340,7 @@ module ActionController #:nodoc: end end - def unmask_token(masked_token) + def unmask_token(masked_token) # :doc: # Split the token into the one-time pad and the encrypted # value and decrypt it one_time_pad = masked_token[0...AUTHENTICITY_TOKEN_LENGTH] @@ -348,11 +348,11 @@ module ActionController #:nodoc: xor_byte_strings(one_time_pad, encrypted_csrf_token) end - def compare_with_real_token(token, session) + def compare_with_real_token(token, session) # :doc: ActiveSupport::SecurityUtils.secure_compare(token, real_csrf_token(session)) end - def valid_per_form_csrf_token?(token, session) + def valid_per_form_csrf_token?(token, session) # :doc: if per_form_csrf_tokens correct_token = per_form_csrf_token( session, @@ -366,12 +366,12 @@ module ActionController #:nodoc: end end - def real_csrf_token(session) + def real_csrf_token(session) # :doc: session[:_csrf_token] ||= SecureRandom.base64(AUTHENTICITY_TOKEN_LENGTH) Base64.strict_decode64(session[:_csrf_token]) end - def per_form_csrf_token(session, action_path, method) + def per_form_csrf_token(session, action_path, method) # :doc: OpenSSL::HMAC.digest( OpenSSL::Digest::SHA256.new, real_csrf_token(session), @@ -379,25 +379,25 @@ module ActionController #:nodoc: ) end - def xor_byte_strings(s1, s2) + def xor_byte_strings(s1, s2) # :doc: s2_bytes = s2.bytes s1.each_byte.with_index { |c1, i| s2_bytes[i] ^= c1 } s2_bytes.pack("C*") end # The form's authenticity parameter. Override to provide your own. - def form_authenticity_param + def form_authenticity_param # :doc: params[request_forgery_protection_token] end # Checks if the controller allows forgery protection. - def protect_against_forgery? + def protect_against_forgery? # :doc: allow_forgery_protection end # Checks if the request originated from the same origin by looking at the # Origin header. - def valid_request_origin? + def valid_request_origin? # :doc: if forgery_protection_origin_check # We accept blank origin headers because some user agents don't send it. request.origin.nil? || request.origin == request.base_url @@ -406,7 +406,7 @@ module ActionController #:nodoc: end end - def normalize_action_path(action_path) + def normalize_action_path(action_path) # :doc: uri = URI.parse(action_path) uri.path.chomp("/") end diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb index 481f19f1ef..877a08b222 100644 --- a/actionpack/lib/action_controller/metal/streaming.rb +++ b/actionpack/lib/action_controller/metal/streaming.rb @@ -193,10 +193,10 @@ module ActionController #:nodoc: module Streaming extend ActiveSupport::Concern - protected + private # Set proper cache control and transfer encoding when streaming - def _process_options(options) #:nodoc: + def _process_options(options) super if options[:stream] if request.version == "HTTP/1.0" @@ -210,7 +210,7 @@ module ActionController #:nodoc: end # Call render_body if we are streaming instead of usual +render+. - def _render_template(options) #:nodoc: + def _render_template(options) if options.delete(:stream) Rack::Chunked::Body.new view_renderer.render_body(view_context, options) else 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 85f2501d42..7229c67f30 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -3,6 +3,7 @@ require "active_support/core_ext/hash/conversions" require "active_support/core_ext/object/to_query" require "active_support/core_ext/module/anonymous" require "active_support/core_ext/hash/keys" +require "active_support/testing/constant_lookup" require "action_controller/template_assertions" require "rails-dom-testing" @@ -353,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) @@ -514,8 +515,6 @@ module ActionController @request = @controller.request @response = @controller.response - @request.delete_header "HTTP_COOKIE" - if @request.have_cookie_jar? unless @request.cookie_jar.committed? @request.cookie_jar.write(@response) @@ -535,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/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb index e5874a39f6..e584b84d92 100644 --- a/actionpack/lib/action_dispatch/http/filter_parameters.rb +++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb @@ -51,28 +51,28 @@ module ActionDispatch @filtered_path ||= query_string.empty? ? path : "#{path}?#{filtered_query_string}" end - protected + private - def parameter_filter + def parameter_filter # :doc: parameter_filter_for fetch_header("action_dispatch.parameter_filter") { return NULL_PARAM_FILTER } end - def env_filter + def env_filter # :doc: user_key = fetch_header("action_dispatch.parameter_filter") { return NULL_ENV_FILTER } parameter_filter_for(Array(user_key) + ENV_MATCH) end - def parameter_filter_for(filters) + def parameter_filter_for(filters) # :doc: ParameterFilter.new(filters) end KV_RE = "[^&;=]+" PAIR_RE = %r{(#{KV_RE})=(#{KV_RE})} - def filtered_query_string + def filtered_query_string # :doc: query_string.gsub(PAIR_RE) do |_| parameter_filter.filter([[$1, $2]]).first.join("=") end diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb index e5f20003a3..c4fe3a5c09 100644 --- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb +++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb @@ -150,20 +150,20 @@ module ActionDispatch order.include?(Mime::ALL) ? format : nil end - protected + private BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/ - def valid_accept_header + def valid_accept_header # :doc: (xhr? && (accept.present? || content_mime_type)) || (accept.present? && accept !~ BROWSER_LIKE_ACCEPTS) end - def use_accept_header + def use_accept_header # :doc: !self.class.ignore_accept_header end - def format_from_path_extension + def format_from_path_extension # :doc: path = get_header("action_dispatch.original_path") || get_header("PATH_INFO") if match = path && path.match(/\.(\w+)\z/) Mime[match.captures.first] diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb index 58eb8d0baf..1583a8f87f 100644 --- a/actionpack/lib/action_dispatch/http/mime_type.rb +++ b/actionpack/lib/action_dispatch/http/mime_type.rb @@ -1,7 +1,6 @@ # -*- frozen-string-literal: true -*- require "singleton" -require "active_support/core_ext/module/attribute_accessors" require "active_support/core_ext/string/starts_ends_with" module Mime @@ -278,6 +277,8 @@ module Mime def all?; false; 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 :string, :synonyms @@ -295,8 +296,8 @@ module Mime end end - def respond_to_missing?(method, include_private = false) #:nodoc: - method.to_s.ends_with? "?" + def respond_to_missing?(method, include_private = false) + (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 ddd15b748b..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 @@ -45,7 +55,7 @@ module ActionDispatch query_parameters.dup end params.merge!(path_parameters) - params = set_custom_encoding(params) + params = set_binary_encoding(params) set_header("action_dispatch.request.parameters", params) params end @@ -73,21 +83,16 @@ module ActionDispatch private - def set_custom_encoding(params) + def set_binary_encoding(params) action = params[:action] - params.each do |k, v| - if v.is_a?(String) && v.encoding != encoding_template(action, k) - params[k] = v.force_encoding(encoding_template(action, k)) + if controller_class.binary_params_for?(action) + ActionDispatch::Request::Utils.each_param_value(params) do |param| + param.force_encoding ::Encoding::ASCII_8BIT end end - params end - def encoding_template(action, param) - controller_class.encoding_for_param(action, param) - end - def parse_formatted_parameters(parsers) return yield if content_length.zero? || content_mime_type.nil? diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 9986d6e1e9..19fa42ce12 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -69,7 +69,7 @@ module ActionDispatch PASS_NOT_FOUND = Class.new { # :nodoc: def self.action(_); self; end def self.call(_); [404, { "X-Cascade" => "pass" }, []]; end - def self.encoding_for_param(action, param); ::Encoding::UTF_8; end + def self.binary_params_for?(action); false; end } def controller_class @@ -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 357ca56036..dc159596c4 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -227,7 +227,9 @@ module ActionDispatch # :nodoc: return unless content_type new_header_info = parse_content_type(content_type.to_s) prev_header_info = parsed_content_type_header - set_content_type new_header_info.mime_type, new_header_info.charset || prev_header_info.charset || self.class.default_charset + charset = new_header_info.charset || prev_header_info.charset + charset ||= self.class.default_charset unless prev_header_info.mime_type + set_content_type new_header_info.mime_type, charset end # Sets the HTTP response's content MIME type. For example, in the controller @@ -408,7 +410,7 @@ module ActionDispatch # :nodoc: def parse_content_type(content_type) if content_type type, charset = content_type.split(/;\s*charset=/) - type = nil if type.empty? + type = nil if type && type.empty? ContentTypeHeader.new(type, charset) else NullContentTypeHeader @@ -423,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 ee91b11b42..e002755bcf 100644 --- a/actionpack/lib/action_dispatch/journey/parser.rb +++ b/actionpack/lib/action_dispatch/journey/parser.rb @@ -1,198 +1,199 @@ # # DO NOT MODIFY!!!! -# This file is automatically generated by Racc 1.4.11 -# from Racc grammer file "". +# This file is automatically generated by Racc 1.4.14 +# from Racc grammar file "". # -require "racc/parser.rb" +require 'racc/parser.rb' + +# :stopdoc: require "action_dispatch/journey/parser_extras" module ActionDispatch - # :stopdoc: module Journey class Parser < Racc::Parser - ##### State transition tables begin ### - - racc_action_table = [ - 13, 15, 14, 7, 21, 16, 8, 19, 13, 15, - 14, 7, 17, 16, 8, 13, 15, 14, 7, 24, - 16, 8, 13, 15, 14, 7, 19, 16, 8 ] - - racc_action_check = [ - 2, 2, 2, 2, 17, 2, 2, 2, 0, 0, - 0, 0, 1, 0, 0, 19, 19, 19, 19, 20, - 19, 19, 7, 7, 7, 7, 22, 7, 7 ] - - racc_action_pointer = [ - 6, 12, -2, nil, nil, nil, nil, 20, nil, nil, - nil, nil, nil, nil, nil, nil, nil, 4, nil, 13, - 13, nil, 17, nil, nil ] - - racc_action_default = [ - -19, -19, -2, -3, -4, -5, -6, -19, -10, -11, - -12, -13, -14, -15, -16, -17, -18, -19, -1, -19, - -19, 25, -8, -9, -7 ] - - racc_goto_table = [ - 1, 22, 18, 23, nil, nil, nil, 20 ] - - racc_goto_check = [ - 1, 2, 1, 3, nil, nil, nil, 1 ] - - racc_goto_pointer = [ - nil, 0, -18, -16, nil, nil, nil, nil, nil, nil, - nil ] - - racc_goto_default = [ - nil, nil, 2, 3, 4, 5, 6, 9, 10, 11, - 12 ] - - racc_reduce_table = [ - 0, 0, :racc_error, - 2, 11, :_reduce_1, - 1, 11, :_reduce_2, - 1, 11, :_reduce_none, - 1, 12, :_reduce_none, - 1, 12, :_reduce_none, - 1, 12, :_reduce_none, - 3, 15, :_reduce_7, - 3, 13, :_reduce_8, - 3, 13, :_reduce_9, - 1, 16, :_reduce_10, - 1, 14, :_reduce_none, - 1, 14, :_reduce_none, - 1, 14, :_reduce_none, - 1, 14, :_reduce_none, - 1, 19, :_reduce_15, - 1, 17, :_reduce_16, - 1, 18, :_reduce_17, - 1, 20, :_reduce_18 ] - - racc_reduce_n = 19 - - racc_shift_n = 25 - - racc_token_table = { - false => 0, - :error => 1, - :SLASH => 2, - :LITERAL => 3, - :SYMBOL => 4, - :LPAREN => 5, - :RPAREN => 6, - :DOT => 7, - :STAR => 8, - :OR => 9 } - - racc_nt_base = 10 - - racc_use_result_var = false - - Racc_arg = [ - racc_action_table, - racc_action_check, - racc_action_default, - racc_action_pointer, - racc_goto_table, - racc_goto_check, - racc_goto_default, - racc_goto_pointer, - racc_nt_base, - racc_reduce_table, - racc_token_table, - racc_shift_n, - racc_reduce_n, - racc_use_result_var ] - - Racc_token_to_s_table = [ - "$end", - "error", - "SLASH", - "LITERAL", - "SYMBOL", - "LPAREN", - "RPAREN", - "DOT", - "STAR", - "OR", - "$start", - "expressions", - "expression", - "or", - "terminal", - "group", - "star", - "symbol", - "literal", - "slash", - "dot" ] - - Racc_debug_parser = false - - ##### State transition tables end ##### - - # reduce 0 omitted - - def _reduce_1(val, _values) - Cat.new(val.first, val.last) - end - - def _reduce_2(val, _values) - val.first - end - - # reduce 3 omitted - - # reduce 4 omitted - - # reduce 5 omitted - - # reduce 6 omitted - - def _reduce_7(val, _values) - Group.new(val[1]) - end - - def _reduce_8(val, _values) - Or.new([val.first, val.last]) - end - - def _reduce_9(val, _values) - Or.new([val.first, val.last]) - end - - def _reduce_10(val, _values) - Star.new(Symbol.new(val.last)) - end - - # reduce 11 omitted - - # reduce 12 omitted - - # reduce 13 omitted - - # reduce 14 omitted - - def _reduce_15(val, _values) - Slash.new("/") - end - - def _reduce_16(val, _values) - Symbol.new(val.first) - end - - def _reduce_17(val, _values) - Literal.new(val.first) - end - - def _reduce_18(val, _values) - Dot.new(val.first) - end - - def _reduce_none(val, _values) - val[0] - end +##### State transition tables begin ### + +racc_action_table = [ + 13, 15, 14, 7, 19, 16, 8, 19, 13, 15, + 14, 7, 17, 16, 8, 13, 15, 14, 7, 21, + 16, 8, 13, 15, 14, 7, 24, 16, 8 ] + +racc_action_check = [ + 2, 2, 2, 2, 22, 2, 2, 2, 19, 19, + 19, 19, 1, 19, 19, 7, 7, 7, 7, 17, + 7, 7, 0, 0, 0, 0, 20, 0, 0 ] + +racc_action_pointer = [ + 20, 12, -2, nil, nil, nil, nil, 13, nil, nil, + nil, nil, nil, nil, nil, nil, nil, 19, nil, 6, + 20, nil, -5, nil, nil ] + +racc_action_default = [ + -19, -19, -2, -3, -4, -5, -6, -19, -10, -11, + -12, -13, -14, -15, -16, -17, -18, -19, -1, -19, + -19, 25, -8, -9, -7 ] + +racc_goto_table = [ + 1, 22, 18, 23, nil, nil, nil, 20 ] + +racc_goto_check = [ + 1, 2, 1, 3, nil, nil, nil, 1 ] + +racc_goto_pointer = [ + nil, 0, -18, -16, nil, nil, nil, nil, nil, nil, + nil ] + +racc_goto_default = [ + nil, nil, 2, 3, 4, 5, 6, 9, 10, 11, + 12 ] + +racc_reduce_table = [ + 0, 0, :racc_error, + 2, 11, :_reduce_1, + 1, 11, :_reduce_2, + 1, 11, :_reduce_none, + 1, 12, :_reduce_none, + 1, 12, :_reduce_none, + 1, 12, :_reduce_none, + 3, 15, :_reduce_7, + 3, 13, :_reduce_8, + 3, 13, :_reduce_9, + 1, 16, :_reduce_10, + 1, 14, :_reduce_none, + 1, 14, :_reduce_none, + 1, 14, :_reduce_none, + 1, 14, :_reduce_none, + 1, 19, :_reduce_15, + 1, 17, :_reduce_16, + 1, 18, :_reduce_17, + 1, 20, :_reduce_18 ] + +racc_reduce_n = 19 + +racc_shift_n = 25 + +racc_token_table = { + false => 0, + :error => 1, + :SLASH => 2, + :LITERAL => 3, + :SYMBOL => 4, + :LPAREN => 5, + :RPAREN => 6, + :DOT => 7, + :STAR => 8, + :OR => 9 } + +racc_nt_base = 10 + +racc_use_result_var = false + +Racc_arg = [ + racc_action_table, + racc_action_check, + racc_action_default, + racc_action_pointer, + racc_goto_table, + racc_goto_check, + racc_goto_default, + racc_goto_pointer, + racc_nt_base, + racc_reduce_table, + racc_token_table, + racc_shift_n, + racc_reduce_n, + racc_use_result_var ] + +Racc_token_to_s_table = [ + "$end", + "error", + "SLASH", + "LITERAL", + "SYMBOL", + "LPAREN", + "RPAREN", + "DOT", + "STAR", + "OR", + "$start", + "expressions", + "expression", + "or", + "terminal", + "group", + "star", + "symbol", + "literal", + "slash", + "dot" ] + +Racc_debug_parser = false + +##### State transition tables end ##### + +# reduce 0 omitted + +def _reduce_1(val, _values) + Cat.new(val.first, val.last) +end + +def _reduce_2(val, _values) + val.first +end + +# reduce 3 omitted + +# reduce 4 omitted + +# reduce 5 omitted + +# reduce 6 omitted + +def _reduce_7(val, _values) + Group.new(val[1]) +end + +def _reduce_8(val, _values) + Or.new([val.first, val.last]) +end + +def _reduce_9(val, _values) + Or.new([val.first, val.last]) +end + +def _reduce_10(val, _values) + Star.new(Symbol.new(val.last)) +end + +# reduce 11 omitted + +# reduce 12 omitted + +# reduce 13 omitted + +# reduce 14 omitted + +def _reduce_15(val, _values) + Slash.new(val.first) +end + +def _reduce_16(val, _values) + Symbol.new(val.first) +end + +def _reduce_17(val, _values) + Literal.new(val.first) +end + +def _reduce_18(val, _values) + Dot.new(val.first) +end + +def _reduce_none(val, _values) + val[0] +end + end # class Parser - end # module Journey - # :startdoc: -end # module ActionDispatch + end # module Journey + end # module ActionDispatch diff --git a/actionpack/lib/action_dispatch/journey/parser.y b/actionpack/lib/action_dispatch/journey/parser.y index d3f7c4d765..f9b1a7a958 100644 --- a/actionpack/lib/action_dispatch/journey/parser.y +++ b/actionpack/lib/action_dispatch/journey/parser.y @@ -30,7 +30,7 @@ rule | dot ; slash - : SLASH { Slash.new('/') } + : SLASH { Slash.new(val.first) } ; symbol : SYMBOL { Symbol.new(val.first) } @@ -45,5 +45,6 @@ rule end ---- header +# :stopdoc: -require 'action_dispatch/journey/parser_extras' +require "action_dispatch/journey/parser_extras" 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/router/utils.rb b/actionpack/lib/action_dispatch/journey/router/utils.rb index ce5d350763..d641642338 100644 --- a/actionpack/lib/action_dispatch/journey/router/utils.rb +++ b/actionpack/lib/action_dispatch/journey/router/utils.rb @@ -58,12 +58,12 @@ module ActionDispatch uri.gsub(ESCAPED) { |match| [match[1, 2].hex].pack("C") }.force_encoding(encoding) end - protected - def escape(component, pattern) + private + def escape(component, pattern) # :doc: component.gsub(pattern) { |unsafe| percent_encode(unsafe) }.force_encoding(US_ASCII) end - def percent_encode(unsafe) + def percent_encode(unsafe) # :doc: safe = EMPTY.dup unsafe.each_byte { |b| safe << DEC2HEX[b] } safe diff --git a/actionpack/lib/action_dispatch/journey/scanner.rb b/actionpack/lib/action_dispatch/journey/scanner.rb index 4b8c8ab063..7dbb39b26d 100644 --- a/actionpack/lib/action_dispatch/journey/scanner.rb +++ b/actionpack/lib/action_dispatch/journey/scanner.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true require "strscan" module ActionDispatch @@ -35,22 +36,23 @@ module ActionDispatch def scan case # / - when text = @ss.scan(/\//) - [:SLASH, text] + when @ss.skip(/\//) + [:SLASH, "/"] + when @ss.skip(/\(/) + [:LPAREN, "("] + when @ss.skip(/\)/) + [:RPAREN, ")"] + when @ss.skip(/\|/) + [:OR, "|"] + when @ss.skip(/\./) + [:DOT, "."] + when text = @ss.scan(/:\w+/) + [:SYMBOL, text] when text = @ss.scan(/\*\w+/) [:STAR, text] - when text = @ss.scan(/(?<!\\)\(/) - [:LPAREN, text] - when text = @ss.scan(/(?<!\\)\)/) - [:RPAREN, text] - when text = @ss.scan(/\|/) - [:OR, text] - when text = @ss.scan(/\./) - [:DOT, text] - when text = @ss.scan(/(?<!\\):\w+/) - [:SYMBOL, text] - when text = @ss.scan(/(?:[\w%\-~!$&'*+,;=@]|\\:|\\\(|\\\))+/) - [:LITERAL, text.tr('\\', "")] + when text = @ss.scan(/(?:[\w%\-~!$&'*+,;=@]|\\[:()])+/) + text.tr! "\\", "" + [:LITERAL, text] # any char when text = @ss.scan(/./) [:LITERAL, text] 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/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb index 6dddcc6ee1..cbe2f4be4d 100644 --- a/actionpack/lib/action_dispatch/middleware/flash.rb +++ b/actionpack/lib/action_dispatch/middleware/flash.rb @@ -281,7 +281,8 @@ module ActionDispatch @now end - def stringify_array(array) + private + def stringify_array(array) # :doc: array.map do |item| item.kind_of?(Symbol) ? item.to_s : item 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 523eeb5b05..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 @@ -153,9 +153,9 @@ module ActionDispatch @ip ||= calculate_ip end - protected + private - def ips_from(header) + def ips_from(header) # :doc: return [] unless header # Split the comma-separated list into an array of strings ips = header.strip.split(/[,\s]+/) @@ -171,7 +171,7 @@ module ActionDispatch end end - def filter_proxies(ips) + def filter_proxies(ips) # :doc: ips.reject do |ip| @proxies.any? { |proxy| proxy === ip } end diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb index 49b82e7128..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 @@ -27,17 +27,16 @@ module ActionDispatch sid end - protected + private - def initialize_sid + def initialize_sid # :doc: @default_options.delete(:sidbits) @default_options.delete(:secure_random) end - private - def make_request(env) - ActionDispatch::Request.new env - end + def make_request(env) + ActionDispatch::Request.new env + end end module StaleSessionCheck 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/request/utils.rb b/actionpack/lib/action_dispatch/request/utils.rb index 282bdbd2be..01bc871e5f 100644 --- a/actionpack/lib/action_dispatch/request/utils.rb +++ b/actionpack/lib/action_dispatch/request/utils.rb @@ -4,6 +4,17 @@ module ActionDispatch mattr_accessor :perform_deep_munge self.perform_deep_munge = true + def self.each_param_value(params, &block) + case params + when Array + params.each { |element| each_param_value(element, &block) } + when Hash + params.each_value { |value| each_param_value(value, &block) } + when String + block.call params + end + end + def self.normalize_encode_params(params) if perform_deep_munge NoNilParamEncoder.normalize_encode_params params 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 4efde09b8b..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 @@ -996,65 +995,65 @@ module ActionDispatch end private - def merge_path_scope(parent, child) #:nodoc: + def merge_path_scope(parent, child) Mapper.normalize_path("#{parent}/#{child}") end - def merge_shallow_path_scope(parent, child) #:nodoc: + def merge_shallow_path_scope(parent, child) Mapper.normalize_path("#{parent}/#{child}") end - def merge_as_scope(parent, child) #:nodoc: + def merge_as_scope(parent, child) parent ? "#{parent}_#{child}" : child end - def merge_shallow_prefix_scope(parent, child) #:nodoc: + def merge_shallow_prefix_scope(parent, child) parent ? "#{parent}_#{child}" : child end - def merge_module_scope(parent, child) #:nodoc: + def merge_module_scope(parent, child) parent ? "#{parent}/#{child}" : child end - def merge_controller_scope(parent, child) #:nodoc: + def merge_controller_scope(parent, child) child end - def merge_action_scope(parent, child) #:nodoc: + def merge_action_scope(parent, child) child end - def merge_via_scope(parent, child) #:nodoc: + def merge_via_scope(parent, child) child end - def merge_format_scope(parent, child) #:nodoc: + def merge_format_scope(parent, child) child end - def merge_path_names_scope(parent, child) #:nodoc: + def merge_path_names_scope(parent, child) merge_options_scope(parent, child) end - def merge_constraints_scope(parent, child) #:nodoc: + def merge_constraints_scope(parent, child) merge_options_scope(parent, child) end - def merge_defaults_scope(parent, child) #:nodoc: + def merge_defaults_scope(parent, child) merge_options_scope(parent, child) end - def merge_blocks_scope(parent, child) #:nodoc: + def merge_blocks_scope(parent, child) merged = parent ? parent.dup : [] merged << child if child merged end - def merge_options_scope(parent, child) #:nodoc: + def merge_options_scope(parent, child) (parent || {}).merge(child) end - def merge_shallow_scope(parent, child) #:nodoc: + def merge_shallow_scope(parent, child) child ? true : false end @@ -1619,13 +1618,13 @@ module ActionDispatch end end - protected + private - def parent_resource #:nodoc: + def parent_resource @scope[:scope_level_resource] end - def apply_common_behavior_for(method, resources, options, &block) #:nodoc: + def apply_common_behavior_for(method, resources, options, &block) if resources.length > 1 resources.each { |r| send(method, r, options, &block) } return true @@ -1658,39 +1657,39 @@ module ActionDispatch false end - def apply_action_options(options) # :nodoc: + def apply_action_options(options) return options if action_options? options options.merge scope_action_options end - def action_options?(options) #:nodoc: + def action_options?(options) options[:only] || options[:except] end - def scope_action_options #:nodoc: + def scope_action_options @scope[:action_options] || {} end - def resource_scope? #:nodoc: + def resource_scope? @scope.resource_scope? end - def resource_method_scope? #:nodoc: + def resource_method_scope? @scope.resource_method_scope? end - def nested_scope? #:nodoc: + def nested_scope? @scope.nested? end - def with_scope_level(kind) + def with_scope_level(kind) # :doc: @scope = @scope.new_level(kind) yield ensure @scope = @scope.parent end - def resource_scope(resource) #:nodoc: + def resource_scope(resource) @scope = @scope.new(scope_level_resource: resource) controller(resource.resource_scope) { yield } @@ -1698,7 +1697,7 @@ module ActionDispatch @scope = @scope.parent end - def nested_options #:nodoc: + def nested_options options = { as: parent_resource.member_name } options[:constraints] = { parent_resource.nested_param => param_constraint @@ -1707,25 +1706,25 @@ module ActionDispatch options end - def shallow_nesting_depth #:nodoc: + def shallow_nesting_depth @scope.find_all { |node| node.frame[:scope_level_resource] }.count { |node| node.frame[:scope_level_resource].shallow? } end - def param_constraint? #:nodoc: + def param_constraint? @scope[:constraints] && @scope[:constraints][parent_resource.param].is_a?(Regexp) end - def param_constraint #:nodoc: + def param_constraint @scope[:constraints][parent_resource.param] end - def canonical_action?(action) #:nodoc: + def canonical_action?(action) resource_method_scope? && CANONICAL_ACTIONS.include?(action.to_s) end - def shallow_scope #:nodoc: + def shallow_scope scope = { as: @scope[:shallow_prefix], path: @scope[:shallow_path] } @scope = @scope.new scope @@ -1735,7 +1734,7 @@ module ActionDispatch @scope = @scope.parent end - def path_for_action(action, path) #:nodoc: + def path_for_action(action, path) return "#{@scope[:path]}/#{path}" if path if canonical_action?(action) @@ -1745,11 +1744,11 @@ module ActionDispatch end end - def action_path(name) #:nodoc: + def action_path(name) @scope[:path_names][name.to_sym] || name end - def prefix_name_for_action(as, action) #:nodoc: + def prefix_name_for_action(as, action) if as prefix = as elsif !canonical_action?(action) @@ -1761,7 +1760,7 @@ module ActionDispatch end end - def name_for_action(as, action) #:nodoc: + def name_for_action(as, action) prefix = prefix_name_for_action(as, action) name_prefix = @scope[:as] @@ -1787,7 +1786,7 @@ module ActionDispatch end end - def set_member_mappings_for_resource + def set_member_mappings_for_resource # :doc: member do get :edit if parent_resource.actions.include?(:edit) get :show if parent_resource.actions.include?(:show) @@ -1799,12 +1798,10 @@ module ActionDispatch end end - def api_only? + def api_only? # :doc: @set.api_only? end - private - def path_scope(path) @scope = @scope.new(path: merge_path_scope(@scope[:path], path)) yield @@ -1868,7 +1865,7 @@ module ActionDispatch path =~ %r{^/?[-\w]+/[-\w/]+$} end - def decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) # :nodoc: + def decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) if on = options.delete(:on) send(on) { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) } else @@ -1883,7 +1880,7 @@ module ActionDispatch end end - def add_route(action, controller, options, _path, to, via, formatted, anchor, options_constraints) # :nodoc: + def add_route(action, controller, options, _path, to, via, formatted, anchor, options_constraints) path = path_for_action(action, _path) raise ArgumentError, "path is required" if path.blank? 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/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb index a1ac5a2b6c..3e564f13d8 100644 --- a/actionpack/lib/action_dispatch/routing/url_for.rb +++ b/actionpack/lib/action_dispatch/routing/url_for.rb @@ -198,14 +198,16 @@ module ActionDispatch _routes.optimize_routes_generation? && default_url_options.empty? end - def _with_routes(routes) + private + + def _with_routes(routes) # :doc: old_routes, @_routes = @_routes, routes yield ensure @_routes = old_routes end - def _routes_context + def _routes_context # :doc: self end 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 1ab6158c90..5fa0b727ab 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -2,7 +2,6 @@ require "stringio" require "uri" require "active_support/core_ext/kernel/singleton_class" require "active_support/core_ext/object/try" -require "active_support/core_ext/string/strip" require "rack/test" require "minitest" @@ -69,7 +68,7 @@ module ActionDispatch DEFAULT_HOST = "www.example.com" include Minitest::Assertions - include RequestHelpers, Assertions + include TestProcess, RequestHelpers, Assertions %w( status status_message headers body redirect? ).each do |method| delegate method, to: :response, allow_nil: true @@ -145,8 +144,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 @@ -598,7 +597,7 @@ module ActionDispatch # Consult the Rails Testing Guide for more. class IntegrationTest < ActiveSupport::TestCase - include TestProcess + include TestProcess::FixtureFile module UrlOptions extend ActiveSupport::Concern diff --git a/actionpack/lib/action_dispatch/testing/test_process.rb b/actionpack/lib/action_dispatch/testing/test_process.rb index 8b03b776fa..0282eb15c3 100644 --- a/actionpack/lib/action_dispatch/testing/test_process.rb +++ b/actionpack/lib/action_dispatch/testing/test_process.rb @@ -3,6 +3,26 @@ require "action_dispatch/middleware/flash" module ActionDispatch module TestProcess + module FixtureFile + # Shortcut for <tt>Rack::Test::UploadedFile.new(File.join(ActionDispatch::IntegrationTest.fixture_path, path), type)</tt>: + # + # post :change_avatar, avatar: fixture_file_upload('files/spongebob.png', 'image/png') + # + # To upload binary files on Windows, pass <tt>:binary</tt> as the last parameter. + # This will not affect other platforms: + # + # post :change_avatar, avatar: fixture_file_upload('files/spongebob.png', 'image/png', :binary) + def fixture_file_upload(path, mime_type = nil, binary = false) + if self.class.respond_to?(:fixture_path) && self.class.fixture_path && + !File.exist?(path) + path = File.join(self.class.fixture_path, path) + end + Rack::Test::UploadedFile.new(path, mime_type, binary) + end + end + + include FixtureFile + def assigns(key = nil) raise NoMethodError, "assigns has been extracted to a gem. To continue using it, @@ -24,21 +44,5 @@ module ActionDispatch def redirect_to_url @response.redirect_url end - - # Shortcut for <tt>Rack::Test::UploadedFile.new(File.join(ActionDispatch::IntegrationTest.fixture_path, path), type)</tt>: - # - # post :change_avatar, avatar: fixture_file_upload('files/spongebob.png', 'image/png') - # - # To upload binary files on Windows, pass <tt>:binary</tt> as the last parameter. - # This will not affect other platforms: - # - # post :change_avatar, avatar: fixture_file_upload('files/spongebob.png', 'image/png', :binary) - def fixture_file_upload(path, mime_type = nil, binary = false) - if self.class.respond_to?(:fixture_path) && self.class.fixture_path && - !File.exist?(path) - path = File.join(self.class.fixture_path, path) - end - Rack::Test::UploadedFile.new(path, mime_type, binary) - end end end 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 e0fa1fbab8..5f1463cfa8 100644 --- a/actionpack/test/controller/filters_test.rb +++ b/actionpack/test/controller/filters_test.rb @@ -55,7 +55,7 @@ class FilterTest < ActionController::TestCase end end - protected + private (1..3).each do |i| define_method "try_#{i}" do instance_variable_set :@try, i @@ -296,7 +296,7 @@ class FilterTest < ActionController::TestCase render inline: "ran action" end - protected + private def find_user @ran_filter ||= [] @ran_filter << "find_user" @@ -428,7 +428,7 @@ class FilterTest < ActionController::TestCase render plain: "bar" end - protected + private def first @first = true end @@ -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 @@ -1040,7 +1040,7 @@ class YieldingAroundFiltersTest < ActionController::TestCase assert_equal 3, controller.instance_variable_get(:@try) end - protected + private def test_process(controller, action = "show") @controller = controller.is_a?(Class) ? controller.new : controller process(action) 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 d3aa81a0f7..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) @@ -289,7 +289,7 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest assert_response :success assert_equal "bar", body - assert_equal nil, headers["Set-Cookie"] + assert_nil headers["Set-Cookie"] assert_equal({ "foo" => "bar" }, cookies.to_hash) end end @@ -308,7 +308,7 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest assert_response :success assert_equal "bar", body - assert_equal nil, headers["Set-Cookie"] + assert_nil headers["Set-Cookie"] assert_equal({ "foo" => "bar" }, cookies.to_hash) end end @@ -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/accept_format_test.rb b/actionpack/test/controller/mime/accept_format_test.rb index a2834c9f39..a22fa39051 100644 --- a/actionpack/test/controller/mime/accept_format_test.rb +++ b/actionpack/test/controller/mime/accept_format_test.rb @@ -40,7 +40,7 @@ class PostController < AbstractPostController respond_to(:html, :iphone, :js) end -protected +private def with_iphone request.format = "iphone" if request.env["HTTP_ACCEPT"] == "text/iphone" diff --git a/actionpack/test/controller/mime/respond_to_test.rb b/actionpack/test/controller/mime/respond_to_test.rb index c5f8165d04..61bd5c80c4 100644 --- a/actionpack/test/controller/mime/respond_to_test.rb +++ b/actionpack/test/controller/mime/respond_to_test.rb @@ -273,7 +273,7 @@ class RespondToController < ActionController::Base end end - protected + private def set_layout case action_name when "all_types_with_layout", "iphone_with_html_response_type" @@ -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/new_base/bare_metal_test.rb b/actionpack/test/controller/new_base/bare_metal_test.rb index 9956f0b107..054757fab3 100644 --- a/actionpack/test/controller/new_base/bare_metal_test.rb +++ b/actionpack/test/controller/new_base/bare_metal_test.rb @@ -52,7 +52,7 @@ module BareMetalTest controller.set_request!(ActionDispatch::Request.empty) controller.set_response!(BareController.make_response!(controller.request)) controller.index - assert_equal nil, controller.response_body + assert_nil controller.response_body end end diff --git a/actionpack/test/controller/new_base/base_test.rb b/actionpack/test/controller/new_base/base_test.rb index 3765d406fc..b891df4c0f 100644 --- a/actionpack/test/controller/new_base/base_test.rb +++ b/actionpack/test/controller/new_base/base_test.rb @@ -25,7 +25,7 @@ module Dispatching render body: "actions: #{action_methods.to_a.sort.join(', ')}" end - protected + private def authenticate end end diff --git a/actionpack/test/controller/new_base/render_context_test.rb b/actionpack/test/controller/new_base/render_context_test.rb index b64468a94e..25b73ac78c 100644 --- a/actionpack/test/controller/new_base/render_context_test.rb +++ b/actionpack/test/controller/new_base/render_context_test.rb @@ -26,16 +26,14 @@ module RenderContext render action: "hello_world", layout: "basic" end - protected - - # 3) Set view_context to self - def view_context - self - end + protected def __controller_method__ + "controller context!" + end - def __controller_method__ - "controller context!" - end + # 3) Set view_context to self + private def view_context + self + end end class RenderContextTest < Rack::TestCase diff --git a/actionpack/test/controller/new_base/render_streaming_test.rb b/actionpack/test/controller/new_base/render_streaming_test.rb index 64b799f826..1177b8b03e 100644 --- a/actionpack/test/controller/new_base/render_streaming_test.rb +++ b/actionpack/test/controller/new_base/render_streaming_test.rb @@ -101,12 +101,12 @@ module RenderStreaming assert_body "Hello world, I'm here!" assert_status 200 assert_equal "22", headers["Content-Length"] - assert_equal nil, headers["Transfer-Encoding"] + assert_nil headers["Transfer-Encoding"] end def assert_streaming!(cache = "no-cache") assert_status 200 - assert_equal nil, headers["Content-Length"] + assert_nil headers["Content-Length"] assert_equal "chunked", headers["Transfer-Encoding"] assert_equal cache, headers["Cache-Control"] end diff --git a/actionpack/test/controller/parameter_encoding_test.rb b/actionpack/test/controller/parameter_encoding_test.rb index 7840b4f5c4..234d0bddd1 100644 --- a/actionpack/test/controller/parameter_encoding_test.rb +++ b/actionpack/test/controller/parameter_encoding_test.rb @@ -1,9 +1,8 @@ require "abstract_unit" class ParameterEncodingController < ActionController::Base - parameter_encoding :test_bar, :bar, Encoding::ASCII_8BIT - parameter_encoding :test_baz, :baz, Encoding::ISO_8859_1 - parameter_encoding :test_baz_to_ascii, :baz, Encoding::ASCII_8BIT + skip_parameter_encoding :test_bar + skip_parameter_encoding :test_all_values_encoding def test_foo render body: params[:foo].encoding @@ -13,16 +12,8 @@ class ParameterEncodingController < ActionController::Base render body: params[:bar].encoding end - def test_baz - render body: params[:baz].encoding - end - - def test_no_change_to_baz - render body: params[:baz].encoding - end - - def test_baz_to_ascii - render body: params[:baz].encoding + def test_all_values_encoding + render body: ::JSON.dump(params.values.map(&:encoding).map(&:name)) end end @@ -36,32 +27,18 @@ class ParameterEncodingTest < ActionController::TestCase assert_equal "UTF-8", @response.body end - test "properly transcodes ASCII_8BIT parameters into declared encodings" do + test "properly encodes ASCII_8BIT parameters into binary" do post :test_bar, params: { "foo" => "foo", "bar" => "bar", "baz" => "baz" } assert_response :success assert_equal "ASCII-8BIT", @response.body end - test "properly transcodes ISO_8859_1 parameters into declared encodings" do - post :test_baz, params: { "foo" => "foo", "bar" => "bar", "baz" => "baz" } - - assert_response :success - assert_equal "ISO-8859-1", @response.body - end - - test "does not transcode parameters when not specified" do - post :test_no_change_to_baz, params: { "foo" => "foo", "bar" => "bar", "baz" => "baz" } + test "properly encodes all ASCII_8BIT parameters into binary" do + post :test_all_values_encoding, params: { "foo" => "foo", "bar" => "bar", "baz" => "baz" } assert_response :success - assert_equal "UTF-8", @response.body - end - - test "respects different encoding declarations for a param per action" do - post :test_baz_to_ascii, params: { "foo" => "foo", "bar" => "bar", "baz" => "baz" } - - assert_response :success - assert_equal "ASCII-8BIT", @response.body + assert_equal ["ASCII-8BIT"], JSON.parse(@response.body).uniq end test "does not raise an error when passed a param declared as ASCII-8BIT that contains invalid bytes" do 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 92e134d313..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 @@ -141,7 +149,7 @@ class ParametersPermitTest < ActiveSupport::TestCase permitted = params.permit(:a, c: [], b: []) assert_equal 1, permitted[:a] assert_equal [1, 2, 3], permitted[:b] - assert_equal nil, permitted[:c] + assert_nil permitted[:c] end test "key to empty array: arrays of permitted scalars pass" do @@ -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] @@ -216,7 +219,7 @@ class ParametersPermitTest < ActiveSupport::TestCase test "fetch with a default value of a hash does not mutate the object" do params = ActionController::Parameters.new({}) params.fetch :foo, {} - assert_equal nil, params[:foo] + assert_nil params[:foo] end test "hashes in array values get wrapped" do @@ -254,8 +257,8 @@ class ParametersPermitTest < ActiveSupport::TestCase end test "fetch doesnt raise ParameterMissing exception if there is a default that is nil" do - assert_equal nil, @params.fetch(:foo, nil) - assert_equal nil, @params.fetch(:foo) { nil } + assert_nil @params.fetch(:foo, nil) + assert_nil @params.fetch(:foo) { nil } end test "KeyError in fetch block should not be covered up" do 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/redirect_test.rb b/actionpack/test/controller/redirect_test.rb index 0e61b92bcf..e4e968dfdb 100644 --- a/actionpack/test/controller/redirect_test.rb +++ b/actionpack/test/controller/redirect_test.rb @@ -123,7 +123,7 @@ class RedirectController < ActionController::Base def rescue_errors(e) raise e end - protected + private def dashbord_url(id, message) url_for action: "dashboard", params: { "id" => id, "message" => message } 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 da6fa1388b..1440db00f6 100644 --- a/actionpack/test/controller/request/test_request_test.rb +++ b/actionpack/test/controller/request/test_request_test.rb @@ -8,7 +8,7 @@ class ActionController::TestRequestTest < ActionController::TestCase def test_mutating_session_options_does_not_affect_default_options @request.session_options[:myparam] = 123 - assert_equal nil, ActionController::TestSession::DEFAULT_OPTIONS[:myparam] + assert_nil ActionController::TestSession::DEFAULT_OPTIONS[:myparam] end def test_content_length_has_bytes_count_value @@ -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/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb index 90d5ab3c67..d645ddfdbe 100644 --- a/actionpack/test/controller/request_forgery_protection_test.rb +++ b/actionpack/test/controller/request_forgery_protection_test.rb @@ -92,7 +92,7 @@ class PrependProtectForgeryBaseController < ActionController::Base render inline: "OK" end - protected + private def add_called_callback(name) @called_callbacks ||= [] diff --git a/actionpack/test/controller/rescue_test.rb b/actionpack/test/controller/rescue_test.rb index a98e6479b7..9ae22c4554 100644 --- a/actionpack/test/controller/rescue_test.rb +++ b/actionpack/test/controller/rescue_test.rb @@ -149,7 +149,7 @@ class RescueController < ActionController::Base raise RangeError end - protected + private def deny_access head :forbidden end @@ -327,7 +327,7 @@ class RescueTest < ActionDispatch::IntegrationTest raise "b00m" end - protected + private def show_errors(exception) render plain: exception.message end diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb index 0b3dc6c41f..fad34dacce 100644 --- a/actionpack/test/controller/resources_test.rb +++ b/actionpack/test/controller/resources_test.rb @@ -1089,7 +1089,7 @@ class ResourcesTest < ActionController::TestCase end end - protected + private def with_restful_routing(*args) options = args.extract_options! collection_methods = options.delete(:collection) diff --git a/actionpack/test/controller/send_file_test.rb b/actionpack/test/controller/send_file_test.rb index a28283f4d6..9e6b975fe2 100644 --- a/actionpack/test/controller/send_file_test.rb +++ b/actionpack/test/controller/send_file_test.rb @@ -241,10 +241,17 @@ class SendFileTest < ActionController::TestCase assert_equal "text/calendar; charset=utf-8", response.headers["Content-Type"] end + def test_send_file_charset_with_type_options_key_without_charset + @controller = SendFileWithActionControllerLive.new + @controller.options = { type: "image/png" } + response = process("file") + assert_equal "image/png", response.headers["Content-Type"] + end + def test_send_file_charset_with_content_type_options_key @controller = SendFileWithActionControllerLive.new @controller.options = { content_type: "text/calendar" } response = process("file") - assert_equal "text/calendar; charset=utf-8", response.headers["Content-Type"] + assert_equal "text/calendar", response.headers["Content-Type"] end end diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb index 05aa4ff6ad..e084e45373 100644 --- a/actionpack/test/controller/test_case_test.rb +++ b/actionpack/test/controller/test_case_test.rb @@ -100,11 +100,11 @@ HTML end def test_xml_output - response.content_type = "application/xml" + response.content_type = params[:response_as] render plain: <<XML <?xml version="1.0" encoding="UTF-8"?> <root> - <area>area is an empty tag in HTML, raising an error if not in xml mode</area> + <area><p>area is an empty tag in HTML, so it won't contain this content</p></area> </root> XML end @@ -374,18 +374,18 @@ XML assert_equal "OK", @response.body end - def test_should_not_impose_childless_html_tags_in_xml - process :test_xml_output + def test_should_impose_childless_html_tags_in_html + process :test_xml_output, params: { response_as: "text/html" } - begin - $stderr = StringIO.new - assert_select "area" #This will cause a warning if content is processed as HTML - $stderr.rewind && err = $stderr.read - ensure - $stderr = STDERR - end + # <area> auto-closes, so the <p> becomes a sibling + assert_select "root > area + p" + end - assert err.empty?, err.inspect + def test_should_not_impose_childless_html_tags_in_xml + process :test_xml_output, params: { response_as: "application/xml" } + + # <area> is not special, so the <p> is its child + assert_select "root > area > p" end def test_assert_generates @@ -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 @@ -796,7 +810,6 @@ XML new_content_type = "new content_type" file.content_type = new_content_type assert_equal new_content_type, file.content_type - end def test_fixture_path_is_accessed_from_self_instead_of_active_support_test_case 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/cookies_test.rb b/actionpack/test/dispatch/cookies_test.rb index af3036d448..664faa31bb 100644 --- a/actionpack/test/dispatch/cookies_test.rb +++ b/actionpack/test/dispatch/cookies_test.rb @@ -272,6 +272,10 @@ class CookiesTest < ActionController::TestCase def noop head :ok end + + def encrypted_cookie + cookies.encrypted["foo"] + end end tests TestController @@ -940,8 +944,8 @@ class CookiesTest < ActionController::TestCase @request.headers["Cookie"] = "user_id=45" get :get_signed_cookie - assert_equal nil, @controller.send(:cookies).signed[:user_id] - assert_equal nil, @response.cookies["user_id"] + assert_nil @controller.send(:cookies).signed[:user_id] + assert_nil @response.cookies["user_id"] end def test_legacy_signed_cookie_is_treated_as_nil_by_encrypted_cookie_jar_if_tampered @@ -951,8 +955,8 @@ class CookiesTest < ActionController::TestCase @request.headers["Cookie"] = "foo=baz" get :get_encrypted_cookie - assert_equal nil, @controller.send(:cookies).encrypted[:foo] - assert_equal nil, @response.cookies["foo"] + assert_nil @controller.send(:cookies).encrypted[:foo] + assert_nil @response.cookies["foo"] end def test_cookie_with_all_domain_option @@ -1189,6 +1193,12 @@ class CookiesTest < ActionController::TestCase assert_equal "david", cookies[:user_name] end + def test_cookies_are_not_cleared + cookies.encrypted["foo"] = "bar" + get :noop + assert_equal "bar", @controller.encrypted_cookie + end + private def assert_cookie_header(expected) header = @response.headers["Set-Cookie"] 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..9eb78fe059 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,25 @@ 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 - @response ||= "response" - @reloader ||= Reloader.new(block || proc { [200, {}, @response] }, x) - @reloader.call("rack.input" => StringIO.new(""))[2] + def reloader(check = lambda { true }) + @reloader ||= begin + reloader = Class.new(ActiveSupport::Reloader) + reloader.check = check + reloader + end end end diff --git a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb index eb4bb14ed1..01c5ff1429 100644 --- a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb @@ -129,7 +129,7 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest params = parse_multipart("none") assert_equal %w(submit-name), params.keys.sort assert_equal "Larry", params["submit-name"] - assert_equal nil, params["files"] + assert_nil params["files"] end test "parses empty upload file" do diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index 2f41851598..2f9228a62d 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -18,7 +18,7 @@ class BaseRequestTest < ActiveSupport::TestCase ActionDispatch::Http::URL.url_for(options) end - protected + private def stub_request(env = {}) ip_spoofing_check = env.key?(:ip_spoofing_check) ? env.delete(:ip_spoofing_check) : true @trusted_proxies ||= nil @@ -94,13 +94,13 @@ class RequestIP < BaseRequestTest assert_equal "3.4.5.6", request.remote_ip request = stub_request "HTTP_X_FORWARDED_FOR" => "unknown,192.168.0.1" - assert_equal nil, request.remote_ip + assert_nil request.remote_ip request = stub_request "HTTP_X_FORWARDED_FOR" => "9.9.9.9, 3.4.5.6, 172.31.4.4, 10.0.0.1" assert_equal "3.4.5.6", request.remote_ip request = stub_request "HTTP_X_FORWARDED_FOR" => "not_ip_address" - assert_equal nil, request.remote_ip + assert_nil request.remote_ip end test "remote ip spoof detection" do @@ -154,7 +154,7 @@ class RequestIP < BaseRequestTest assert_equal "fe80:0000:0000:0000:0202:b3ff:fe1e:8329", request.remote_ip request = stub_request "HTTP_X_FORWARDED_FOR" => "unknown,::1" - assert_equal nil, request.remote_ip + assert_nil request.remote_ip request = stub_request "HTTP_X_FORWARDED_FOR" => "2001:0db8:85a3:0000:0000:8a2e:0370:7334, fe80:0000:0000:0000:0202:b3ff:fe1e:8329, ::1, fc00::, fc01::, fdff" assert_equal "fe80:0000:0000:0000:0202:b3ff:fe1e:8329", request.remote_ip @@ -163,7 +163,7 @@ class RequestIP < BaseRequestTest assert_equal "FE00::", request.remote_ip request = stub_request "HTTP_X_FORWARDED_FOR" => "not_ip_address" - assert_equal nil, request.remote_ip + assert_nil request.remote_ip end test "remote ip v6 spoof detection" do @@ -200,7 +200,7 @@ class RequestIP < BaseRequestTest assert_equal "3.4.5.6", request.remote_ip request = stub_request "HTTP_X_FORWARDED_FOR" => "67.205.106.73,unknown" - assert_equal nil, request.remote_ip + assert_nil request.remote_ip request = stub_request "HTTP_X_FORWARDED_FOR" => "9.9.9.9, 3.4.5.6, 10.0.0.1, 67.205.106.73" assert_equal "3.4.5.6", request.remote_ip @@ -222,7 +222,7 @@ class RequestIP < BaseRequestTest assert_equal "::1", request.remote_ip request = stub_request "HTTP_X_FORWARDED_FOR" => "unknown,fe80:0000:0000:0000:0202:b3ff:fe1e:8329" - assert_equal nil, request.remote_ip + assert_nil request.remote_ip request = stub_request "HTTP_X_FORWARDED_FOR" => "fe80:0000:0000:0000:0202:b3ff:fe1e:8329,2001:0db8:85a3:0000:0000:8a2e:0370:7334" assert_equal "2001:0db8:85a3:0000:0000:8a2e:0370:7334", request.remote_ip @@ -345,7 +345,7 @@ class RequestPort < BaseRequestTest test "optional port" do request = stub_request "HTTP_HOST" => "www.example.org:80" - assert_equal nil, request.optional_port + assert_nil request.optional_port request = stub_request "HTTP_HOST" => "www.example.org:8080" assert_equal 8080, request.optional_port @@ -537,7 +537,7 @@ class RequestCGI < BaseRequestTest assert_equal "Basic", request.auth_type assert_equal 0, request.content_length - assert_equal nil, request.content_mime_type + assert_nil request.content_mime_type assert_equal "CGI/1.1", request.gateway_interface assert_equal "*/*", request.accept assert_equal "UTF-8", request.accept_charset @@ -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 @@ -957,7 +957,7 @@ class RequestMimeType < BaseRequestTest end test "no content type" do - assert_equal nil, stub_request.content_mime_type + assert_nil stub_request.content_mime_type end test "content type is XML" do @@ -978,7 +978,7 @@ class RequestMimeType < BaseRequestTest "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest" ) - assert_equal nil, request.negotiate_mime([Mime[:xml], Mime[:json]]) + assert_nil request.negotiate_mime([Mime[:xml], Mime[:json]]) assert_equal Mime[:html], request.negotiate_mime([Mime[:xml], Mime[:html]]) assert_equal Mime[:html], request.negotiate_mime([Mime[:xml], Mime::ALL]) end @@ -1192,7 +1192,7 @@ class RequestEtag < BaseRequestTest test "doesn't match absent If-None-Match" do request = stub_request - assert_equal nil, request.if_none_match + assert_nil request.if_none_match assert_equal [], request.if_none_match_etags assert_not request.etag_matches?("foo") diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb index 400af42bac..7433c5ce0c 100644 --- a/actionpack/test/dispatch/response_test.rb +++ b/actionpack/test/dispatch/response_test.rb @@ -74,7 +74,7 @@ class ResponseTest < ActiveSupport::TestCase @response.body = "Hello, World!" # even though there's no explicitly set content-type, - assert_equal nil, @response.content_type + assert_nil @response.content_type # after the action reads back @response.body, assert_equal "Hello, World!", @response.body @@ -110,6 +110,11 @@ class ResponseTest < ActiveSupport::TestCase assert_equal "application/aaron", @response.content_type end + def test_empty_content_type_returns_nil + @response.headers["Content-Type"] = "" + assert_nil @response.content_type + end + test "simple output" do @response.body = "Hello, World!" diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index 92d323f292..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" @@ -4189,7 +4189,7 @@ class TestConstraintsAccessingParameters < ActionDispatch::IntegrationTest test "parameters are reset between constraint checks" do get "/bar" - assert_equal nil, @request.params[:foo] + assert_nil @request.params[:foo] assert_equal "bar", @request.params[:bar] end end diff --git a/actionpack/test/dispatch/runner_test.rb b/actionpack/test/dispatch/runner_test.rb index 969933c9ed..b76bf4a320 100644 --- a/actionpack/test/dispatch/runner_test.rb +++ b/actionpack/test/dispatch/runner_test.rb @@ -4,7 +4,6 @@ class RunnerTest < ActiveSupport::TestCase test "runner preserves the setting of integration_session" do runner = Class.new do def before_setup - end end.new diff --git a/actionpack/test/dispatch/session/cache_store_test.rb b/actionpack/test/dispatch/session/cache_store_test.rb index a60629a7ee..859059063f 100644 --- a/actionpack/test/dispatch/session/cache_store_test.rb +++ b/actionpack/test/dispatch/session/cache_store_test.rb @@ -142,20 +142,20 @@ class CacheStoreTest < ActionDispatch::IntegrationTest get "/get_session_value" assert_response :success - assert_equal nil, headers["Set-Cookie"], "should not resend the cookie again if session_id cookie is already exists" + assert_nil headers["Set-Cookie"], "should not resend the cookie again if session_id cookie is already exists" end end def test_prevents_session_fixation with_test_route_set do - assert_equal nil, @cache.read("_session_id:0xhax") + assert_nil @cache.read("_session_id:0xhax") cookies["_session_id"] = "0xhax" get "/set_session_value" assert_response :success assert_not_equal "0xhax", cookies["_session_id"] - assert_equal nil, @cache.read("_session_id:0xhax") + assert_nil @cache.read("_session_id:0xhax") assert_equal({ "foo" => "bar" }, @cache.read("_session_id:#{cookies['_session_id']}")) end end diff --git a/actionpack/test/dispatch/session/cookie_store_test.rb b/actionpack/test/dispatch/session/cookie_store_test.rb index 013d289c6d..63dfc07c0d 100644 --- a/actionpack/test/dispatch/session/cookie_store_test.rb +++ b/actionpack/test/dispatch/session/cookie_store_test.rb @@ -108,7 +108,7 @@ class CookieStoreTest < ActionDispatch::IntegrationTest with_test_route_set(secure: true) do get "/set_session_value" assert_response :success - assert_equal nil, headers["Set-Cookie"] + assert_nil headers["Set-Cookie"] end end @@ -169,17 +169,17 @@ class CookieStoreTest < ActionDispatch::IntegrationTest with_test_route_set do get "/no_session_access" assert_response :success - assert_equal nil, headers["Set-Cookie"] + assert_nil headers["Set-Cookie"] end end 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 - assert_equal nil, headers["Set-Cookie"] + assert_nil headers["Set-Cookie"] end end diff --git a/actionpack/test/dispatch/session/mem_cache_store_test.rb b/actionpack/test/dispatch/session/mem_cache_store_test.rb index c2d0719b4e..121e9ebef7 100644 --- a/actionpack/test/dispatch/session/mem_cache_store_test.rb +++ b/actionpack/test/dispatch/session/mem_cache_store_test.rb @@ -157,7 +157,7 @@ class MemCacheStoreTest < ActionDispatch::IntegrationTest get "/get_session_value" assert_response :success - assert_equal nil, headers["Set-Cookie"], "should not resend the cookie again if session_id cookie is already exists" + assert_nil headers["Set-Cookie"], "should not resend the cookie again if session_id cookie is already exists" end rescue Dalli::RingError => ex skip ex.message, ex.backtrace 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 3facbf59c2..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 @@ -177,9 +177,9 @@ module StaticTests last_modified = File.mtime(File.join(@root, "#{file_name}.gz")) response = get(file_name, "HTTP_ACCEPT_ENCODING" => "gzip", "HTTP_IF_MODIFIED_SINCE" => last_modified.httpdate) assert_equal 304, response.status - assert_equal nil, response.headers["Content-Type"] - assert_equal nil, response.headers["Content-Encoding"] - assert_equal nil, response.headers["Vary"] + assert_nil response.headers["Content-Type"] + assert_nil response.headers["Content-Encoding"] + assert_nil response.headers["Vary"] end def test_serves_files_with_headers diff --git a/actionpack/test/dispatch/test_request_test.rb b/actionpack/test/dispatch/test_request_test.rb index b479af781d..85a6df4975 100644 --- a/actionpack/test/dispatch/test_request_test.rb +++ b/actionpack/test/dispatch/test_request_test.rb @@ -30,7 +30,7 @@ class TestRequestTest < ActiveSupport::TestCase req = ActionDispatch::TestRequest.create({}) assert_equal({}, req.cookies) - assert_equal nil, req.env["HTTP_COOKIE"] + assert_nil req.env["HTTP_COOKIE"] req.cookie_jar["user_name"] = "david" assert_cookies({ "user_name" => "david" }, req.cookie_jar) 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 558659dd77..b071b260c9 100644 --- a/actionview/CHANGELOG.md +++ b/actionview/CHANGELOG.md @@ -1,3 +1,41 @@ +* 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. + + Before it was enabled by `consider_all_requests_local`, which caused + recompilation in tests. + + *Max Melentiev* + * Add `form_with` to unify `form_tag` and `form_for` usage. Used like `form_tag` (where just the open tag is output): 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/RUNNING_UJS_TESTS.rdoc b/actionview/RUNNING_UJS_TESTS.rdoc index cbf6bdccc6..a575624a06 100644 --- a/actionview/RUNNING_UJS_TESTS.rdoc +++ b/actionview/RUNNING_UJS_TESTS.rdoc @@ -4,4 +4,4 @@ Ensure that you can build the project and run tests. Run rake ujs:server first, and then run the web tests by visiting http://localhost:4567 in your browser. -rake test:server +rake ujs:server 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 4e4f4823e6..750f96f29e 100644 --- a/actionview/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb @@ -35,18 +35,37 @@ module ActionView # When the Asset Pipeline is enabled, you can pass the name of your manifest as # source, and include other JavaScript or CoffeeScript files inside the manifest. # + # ==== Options + # + # When the last parameter is a hash you can add HTML attributes using that + # parameter. The following options are supported: + # + # * <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. + # * <tt>:host</tt> - When a relative url is provided the host is added to the + # that path. + # * <tt>:skip_pipeline</tt> - This option is used to bypass the asset pipeline + # when it is set to true. + # + # ==== Examples + # # javascript_include_tag "xmlhr" - # # => <script src="/assets/xmlhr.js?1284139606"></script> + # # => <script src="/assets/xmlhr.debug-1284139606.js"></script> + # + # javascript_include_tag "xmlhr", host: "localhost", protocol: "https" + # # => <script src="https://localhost/assets/xmlhr.debug-1284139606.js"></script> # # javascript_include_tag "template.jst", extname: false - # # => <script src="/assets/template.jst?1284139606"></script> + # # => <script src="/assets/template.debug-1284139606.jst"></script> # # javascript_include_tag "xmlhr.js" - # # => <script src="/assets/xmlhr.js?1284139606"></script> + # # => <script src="/assets/xmlhr.debug-1284139606.js"></script> # # javascript_include_tag "common.javascript", "/elsewhere/cools" - # # => <script src="/assets/common.javascript?1284139606"></script> - # # <script src="/elsewhere/cools.js?1423139606"></script> + # # => <script src="/assets/common.javascript.debug-1284139606.js"></script> + # # <script src="/elsewhere/cools.debug-1284139606.js"></script> # # javascript_include_tag "http://www.example.com/xmlhr" # # => <script src="http://www.example.com/xmlhr"></script> 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 bf1c8ceaed..15ab7e304f 100644 --- a/actionview/lib/action_view/helpers/cache_helper.rb +++ b/actionview/lib/action_view/helpers/cache_helper.rb @@ -215,7 +215,7 @@ module ActionView private - def fragment_name_with_digest(name, virtual_path) #:nodoc: + def fragment_name_with_digest(name, virtual_path) virtual_path ||= @virtual_path if virtual_path name = controller.url_for(name).split("://").last if name.is_a?(Hash) @@ -226,7 +226,7 @@ module ActionView end end - def fragment_for(name = {}, options = nil, &block) #:nodoc: + def fragment_for(name = {}, options = nil, &block) if content = read_fragment_for(name, options) @cache_hit = true content @@ -236,13 +236,11 @@ module ActionView end end - def read_fragment_for(name, options) #:nodoc: + def read_fragment_for(name, options) controller.read_fragment(name, options) end - def write_fragment_for(name, options) #:nodoc: - # VIEW TODO: Make #capture usable outside of ERB - # This dance is needed because Builder can't use capture + def write_fragment_for(name, options) 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 e7ea267211..26a625e4fe 100644 --- a/actionview/lib/action_view/helpers/form_helper.rb +++ b/actionview/lib/action_view/helpers/form_helper.rb @@ -513,6 +513,17 @@ module ActionView # <input type="text" name="post[title]" value="<the title of the post>"> # </form> # + # # Though the fields don't have to correspond to model attributes: + # <%= form_with model: Cat.new do |form| %> + # <%= form.text_field :cats_dont_have_gills %> + # <%= form.text_field :but_in_forms_they_can %> + # <% end %> + # # => + # <form action="/cats" method="post" data-remote="true"> + # <input type="text" name="cat[cats_dont_have_gills]"> + # <input type="text" name="cat[but_in_forms_they_can]"> + # </form> + # # The parameters in the forms are accessible in controllers according to # their name nesting. So inputs named +title+ and <tt>post[title]</tt> are # accessible as <tt>params[:title]</tt> and <tt>params[:post][:title]</tt> @@ -521,7 +532,7 @@ module ActionView # By default +form_with+ attaches the <tt>data-remote</tt> attribute # submitting the form via an XMLHTTPRequest in the background if an # Unobtrusive JavaScript driver, like rails-ujs, is used. See the - # <tt>:remote</tt> option for more. + # <tt>:local</tt> option for more. # # For ease of comparison the examples above left out the submit button, # as well as the auto generated hidden fields that enable UTF-8 support @@ -595,6 +606,16 @@ module ActionView # # Where <tt>@document = Document.find(params[:id])</tt>. # + # When using labels +form_with+ requires setting the id on the field being + # labelled: + # + # <%= form_with(model: @post) do |form| %> + # <%= form.label :title %> + # <%= form.text_field :title, id: :post_title %> + # <% end %> + # + # See +label+ for more on how the +for+ attribute is derived. + # # === Mixing with other form helpers # # While +form_with+ uses a FormBuilder object it's possible to mix and @@ -690,6 +711,9 @@ module ActionView # form_with(**options.merge(builder: LabellingFormBuilder), &block) # end def form_with(model: nil, scope: nil, url: nil, format: nil, **options) + options[:allow_method_names_outside_object] = true + options[:skip_default_ids] = true + if model url ||= polymorphic_path(model, format: format) @@ -964,14 +988,14 @@ module ActionView # <%= fields :comment do |fields| %> # <%= fields.text_field :body %> # <% end %> - # # => <input type="text" name="comment[body] id="comment_body"> + # # => <input type="text" name="comment[body]> # # # Using a model infers the scope and assigns field values: # <%= fields model: Comment.new(body: "full bodied") do |fields| %< # <%= fields.text_field :body %> # <% end %> # # => - # <input type="text" name="comment[body] id="comment_body" value="full bodied"> + # <input type="text" name="comment[body] value="full bodied"> # # # Using +fields+ with +form_with+: # <%= form_with model: @post do |form| %> @@ -986,6 +1010,16 @@ module ActionView # or model is yielded, so any generated field names are prefixed with # either the passed scope or the scope inferred from the <tt>:model</tt>. # + # When using labels +fields+ requires setting the id on the field being + # labelled: + # + # <%= fields :comment do |fields| %> + # <%= fields.label :body %> + # <%= fields.text_field :body, id: :comment_body %> + # <% end %> + # + # See +label+ for more on how the +for+ attribute is derived. + # # === Mixing with other form helpers # # While +form_with+ uses a FormBuilder object it's possible to mix and @@ -1003,7 +1037,9 @@ module ActionView # to work with an object as a base, like # FormOptionHelper#collection_select and DateHelper#datetime_select. def fields(scope = nil, model: nil, **options, &block) - # TODO: Remove when ids and classes are no longer output by default. + options[:allow_method_names_outside_object] = true + options[:skip_default_ids] = true + if model scope ||= model_name_from_record_or_class(model).param_key end @@ -1469,7 +1505,7 @@ module ActionView private def html_options_for_form_with(url_for_options = nil, model = nil, html: {}, local: false, skip_enforcing_utf8: false, **options) - html_options = options.except(:index, :include_id, :builder).merge(html) + html_options = options.slice(:id, :class, :multipart, :method, :data).merge(html) html_options[:method] ||= :patch if model.respond_to?(:persisted?) && model.persisted? html_options[:enforce_utf8] = !skip_enforcing_utf8 @@ -1603,7 +1639,7 @@ module ActionView def initialize(object_name, object, template, options) @nested_child_index = {} @object_name, @object, @template, @options = object_name, object, template, options - @default_options = @options ? @options.slice(:index, :namespace) : {} + @default_options = @options ? @options.slice(:index, :namespace, :skip_default_ids, :allow_method_names_outside_object) : {} convert_to_legacy_options(@options) @@ -1888,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 @@ -1910,6 +1947,9 @@ module ActionView # See the docs for the <tt>ActionView::FormHelper.fields</tt> helper method. def fields(scope = nil, model: nil, **options, &block) + options[:allow_method_names_outside_object] = true + options[:skip_default_ids] = true + convert_to_legacy_options(options) fields_for(scope || model, model, **options, &block) @@ -2268,10 +2308,6 @@ module ActionView if options.key?(:skip_id) options[:include_id] = !options.delete(:skip_id) end - - if options.key?(:local) - options[:remote] = !options.delete(:local) - end end end end diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb index 7bd473507b..ffc64e7118 100644 --- a/actionview/lib/action_view/helpers/form_tag_helper.rb +++ b/actionview/lib/action_view/helpers/form_tag_helper.rb @@ -442,26 +442,14 @@ 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 # Creates a button element that defines a <tt>submit</tt> button, - # <tt>reset</tt>button or a generic button which can be used in + # <tt>reset</tt> button or a generic button which can be used in # JavaScript, for example. You can use the button tag as a regular # submit tag but it isn't supported in legacy browsers. However, # the button tag does allow for richer labels such as images and emphasis, @@ -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 75b898c3e9..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. @@ -171,6 +171,9 @@ module ActionView # to ","). # * <tt>:separator</tt> - Sets the separator between the # fractional and integer digits (defaults to "."). + # * <tt>:delimiter_pattern</tt> - Sets a custom regular expression used for + # deriving the placement of delimiter. Helpful when using currency formats + # like INR. # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when # the argument is invalid. # @@ -187,6 +190,9 @@ module ActionView # number_with_delimiter(98765432.98, delimiter: " ", separator: ",") # # => 98 765 432,98 # + # number_with_delimiter("123456.78", + # delimiter_pattern: /(\d+?)(?=(\d\d)+(\d)(?!\d))/) # => "1,23,456.78" + # # number_with_delimiter("112a", raise: true) # => raise InvalidNumberError def number_with_delimiter(number, options = {}) delegate_number_helper_method(:number_to_delimited, number, options) 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 cf8a6d6028..0895533a60 100644 --- a/actionview/lib/action_view/helpers/tags/base.rb +++ b/actionview/lib/action_view/helpers/tags/base.rb @@ -13,8 +13,17 @@ module ActionView @object_name.sub!(/\[\]$/, "") || @object_name.sub!(/\[\]\]$/, "]") @object = retrieve_object(options.delete(:object)) + @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. @@ -25,7 +34,11 @@ module ActionView private def value(object) - object.public_send @method_name if object + if @allow_method_names_outside_object + object.public_send @method_name if object && object.respond_to?(@method_name) + else + object.public_send @method_name if object + end end def value_before_type_cast(object) @@ -81,15 +94,21 @@ module ActionView def add_default_name_and_id(options) index = name_and_id_index(options) options["name"] = options.fetch("name") { tag_name(options["multiple"], index) } - options["id"] = options.fetch("id") { tag_id(index) } - if namespace = options.delete("namespace") - options["id"] = options["id"] ? "#{namespace}_#{options['id']}" : namespace + + unless skip_default_ids? + options["id"] = options.fetch("id") { tag_id(index) } + if namespace = options.delete("namespace") + options["id"] = options["id"] ? "#{namespace}_#{options['id']}" : namespace + end end end def tag_name(multiple = false, index = nil) # a little duplication to construct less strings - if index + case + when @object_name.empty? + "#{sanitized_method_name}#{"[]" if multiple}" + when index "#{@object_name}[#{index}][#{sanitized_method_name}]#{"[]" if multiple}" else "#{@object_name}[#{sanitized_method_name}]#{"[]" if multiple}" @@ -98,7 +117,10 @@ module ActionView def tag_id(index = nil) # a little duplication to construct less strings - if index + case + when @object_name.empty? + sanitized_method_name.dup + when index "#{sanitized_object_name}_#{index}_#{sanitized_method_name}" else "#{sanitized_object_name}_#{sanitized_method_name}" @@ -152,7 +174,15 @@ 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? + @skip_default_ids end end end diff --git a/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb b/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb index 0359d4e65d..7252d4f2d9 100644 --- a/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb +++ b/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb @@ -24,7 +24,7 @@ module ActionView builder.check_box + builder.label end - def hidden_field_name #:nodoc: + def hidden_field_name "#{super}[]" end end diff --git a/actionview/lib/action_view/helpers/tags/collection_helpers.rb b/actionview/lib/action_view/helpers/tags/collection_helpers.rb index c8be392865..75d237eb35 100644 --- a/actionview/lib/action_view/helpers/tags/collection_helpers.rb +++ b/actionview/lib/action_view/helpers/tags/collection_helpers.rb @@ -43,7 +43,7 @@ module ActionView # Generate default options for collection helpers, such as :checked and # :disabled. - def default_html_options_for_collection(item, value) #:nodoc: + def default_html_options_for_collection(item, value) html_options = @html_options.dup [:checked, :selected, :disabled, :readonly].each do |option| @@ -67,11 +67,11 @@ module ActionView html_options end - def sanitize_attribute_name(value) #:nodoc: + def sanitize_attribute_name(value) "#{sanitized_method_name}_#{sanitized_value(value)}" end - def render_collection #:nodoc: + def render_collection @collection.map do |item| value = value_for_collection(item, @value_method) text = value_for_collection(item, @text_method) @@ -82,7 +82,7 @@ module ActionView end.join.html_safe end - def render_collection_for(builder_class, &block) #:nodoc: + def render_collection_for(builder_class, &block) options = @options.stringify_keys rendered_collection = render_collection do |item, value, text, default_html_options| builder = instantiate_builder(builder_class, item, value, text, default_html_options) @@ -103,12 +103,12 @@ module ActionView end end - def hidden_field #:nodoc: + def hidden_field hidden_name = @html_options[:name] || hidden_field_name @template_object.hidden_field_tag(hidden_name, "", id: nil) end - def hidden_field_name #:nodoc: + def hidden_field_name "#{tag_name(false, @options[:index])}" end end 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/label.rb b/actionview/lib/action_view/helpers/tags/label.rb index b31d5fda66..cab15ae201 100644 --- a/actionview/lib/action_view/helpers/tags/label.rb +++ b/actionview/lib/action_view/helpers/tags/label.rb @@ -73,6 +73,10 @@ module ActionView def render_component(builder) builder.translation end + + def skip_default_ids? + false # The id is used as the `for` attribute. + end end 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/tags/translator.rb b/actionview/lib/action_view/helpers/tags/translator.rb index 62b1df81c6..ced835eaa8 100644 --- a/actionview/lib/action_view/helpers/tags/translator.rb +++ b/actionview/lib/action_view/helpers/tags/translator.rb @@ -14,6 +14,8 @@ module ActionView translated_attribute || human_attribute_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 :object_name, :method_and_value, :scope, :model 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 1277126995..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 @@ -105,10 +105,9 @@ module ActionView # driver to prompt with the question specified (in this case, the # resulting text would be <tt>question?</tt>. If the user accepts, the # link is processed normally, otherwise no action is taken. - # * <tt>:disable_with</tt> - Value of this parameter will be - # used as the value for a disabled version of the submit - # button when the form is submitted. This feature is provided - # by the unobtrusive JavaScript driver. + # * <tt>:disable_with</tt> - Value of this parameter will be used as the + # name for a disabled version of the link. This feature is provided by + # the unobtrusive JavaScript driver. # # ==== Examples # Because it relies on +url_for+, +link_to+ supports both older-style controller/action/id arguments @@ -518,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 # @@ -531,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 " \ @@ -540,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 != "/" @@ -615,7 +619,7 @@ module ActionView # # to_form_params({ name: 'Denmark' }, 'country') # # => [{name: 'country[name]', value: 'Denmark'}] - def to_form_params(attribute, namespace = nil) # :nodoc: + def to_form_params(attribute, namespace = nil) attribute = if attribute.respond_to?(:permitted?) unless attribute.permitted? raise ArgumentError, "Attempting to generate a buttom from non-sanitized request parameters!" \ diff --git a/actionview/lib/action_view/layouts.rb b/actionview/lib/action_view/layouts.rb index 7499e3b951..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} @@ -338,7 +338,7 @@ module ActionView # # ==== Returns # * <tt>String</tt> - A template name - def _implied_layout_name # :nodoc: + def _implied_layout_name controller_path end end diff --git a/actionview/lib/action_view/log_subscriber.rb b/actionview/lib/action_view/log_subscriber.rb index c9f308c2a2..d03e1a51b8 100644 --- a/actionview/lib/action_view/log_subscriber.rb +++ b/actionview/lib/action_view/log_subscriber.rb @@ -51,20 +51,20 @@ module ActionView ActionView::Base.logger end - protected + private EMPTY = "" - def from_rails_root(string) + def from_rails_root(string) # :doc: string = string.sub(rails_root, EMPTY) string.sub!(VIEWS_PATTERN, EMPTY) string end - def rails_root + def rails_root # :doc: @root ||= "#{Rails.root}/" end - def render_count(payload) + def render_count(payload) # :doc: if payload[:cache_hits] "[#{payload[:cache_hits]} / #{payload[:count]} cache hits]" else @@ -72,7 +72,7 @@ module ActionView end end - def cache_message(payload) + def cache_message(payload) # :doc: if payload[:cache_hit] "[cache hit]" else @@ -80,8 +80,6 @@ module ActionView end end - private - def log_rendering_start(payload) info do message = " Rendering #{from_rails_root(payload[:identifier])}" diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb index 50faf1b8dd..f385a7cd04 100644 --- a/actionview/lib/action_view/lookup_context.rb +++ b/actionview/lib/action_view/lookup_context.rb @@ -93,9 +93,9 @@ module ActionView @cache = old_value end - protected + private - def _set_detail(key, value) + def _set_detail(key, value) # :doc: @details = @details.dup if @details_key @details_key = nil @details[key] = value @@ -149,16 +149,16 @@ module ActionView added_resolvers.times { view_paths.pop } end - protected + private - def args_for_lookup(name, prefixes, partial, keys, details_options) #:nodoc: + def args_for_lookup(name, prefixes, partial, keys, details_options) name, prefixes = normalize_name(name, prefixes) details, details_key = detail_args_for(details_options) [name, prefixes, partial || false, details, details_key, keys] end # Compute details hash and key according to user options (e.g. passed from #render). - def detail_args_for(options) + def detail_args_for(options) # :doc: return @details, details_key if options.empty? # most common path. user_details = @details.merge(options) @@ -171,13 +171,13 @@ module ActionView [user_details, details_key] end - def args_for_any(name, prefixes, partial) # :nodoc: + def args_for_any(name, prefixes, partial) name, prefixes = normalize_name(name, prefixes) details, details_key = detail_args_for_any [name, prefixes, partial || false, details, details_key] end - def detail_args_for_any # :nodoc: + def detail_args_for_any @detail_args_for_any ||= begin details = {} @@ -200,7 +200,7 @@ module ActionView # Support legacy foo.erb names even though we now ignore .erb # as well as incorrectly putting part of the path in the template # name instead of the prefix. - def normalize_name(name, prefixes) #:nodoc: + def normalize_name(name, prefixes) prefixes = prefixes.presence parts = name.to_s.split("/".freeze) parts.shift if parts.first.empty? diff --git a/actionview/lib/action_view/railtie.rb b/actionview/lib/action_view/railtie.rb index ae72cea404..d344d98f4b 100644 --- a/actionview/lib/action_view/railtie.rb +++ b/actionview/lib/action_view/railtie.rb @@ -39,7 +39,7 @@ module ActionView initializer "action_view.per_request_digest_cache" do |app| ActiveSupport.on_load(:action_view) do - if app.config.consider_all_requests_local + unless ActionView::Resolver.caching? app.executor.to_run ActionView::Digestor::PerExecutionDigestCacheExpiry end end diff --git a/actionview/lib/action_view/record_identifier.rb b/actionview/lib/action_view/record_identifier.rb index b39acfa0b5..48bea315a9 100644 --- a/actionview/lib/action_view/record_identifier.rb +++ b/actionview/lib/action_view/record_identifier.rb @@ -92,7 +92,7 @@ module ActionView end end - protected + private # Returns a string representation of the key attribute(s) that is suitable for use in an HTML DOM id. # This can be overwritten to customize the default generated string representation if desired. @@ -102,7 +102,7 @@ module ActionView # overwritten version of the method. By default, this implementation passes the key string through a # method that replaces all characters that are invalid inside DOM ids, with valid ones. You need to # make sure yourself that your dom ids are valid, in case you overwrite this method. - def record_key_for_dom_id(record) + def record_key_for_dom_id(record) # :doc: key = convert_to_model(record).to_key key ? key.join(JOIN) : key end diff --git a/actionview/lib/action_view/renderer/abstract_renderer.rb b/actionview/lib/action_view/renderer/abstract_renderer.rb index 3c85be49cd..0b315eb569 100644 --- a/actionview/lib/action_view/renderer/abstract_renderer.rb +++ b/actionview/lib/action_view/renderer/abstract_renderer.rb @@ -25,9 +25,9 @@ module ActionView raise NotImplementedError end - protected + private - def extract_details(options) + def extract_details(options) # :doc: @lookup_context.registered_details.each_with_object({}) do |key, details| value = options[key] @@ -35,7 +35,7 @@ module ActionView end end - def instrument(name, **options) + def instrument(name, **options) # :doc: options[:identifier] ||= (@template && @template.identifier) || @path ActiveSupport::Notifications.instrument("render_#{name}.action_view", options) do |payload| @@ -43,7 +43,7 @@ module ActionView end end - def prepend_formats(formats) + def prepend_formats(formats) # :doc: formats = Array(formats) return if formats.empty? || @lookup_context.html_fallback_for_js 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 2434250b2d..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,8 +27,7 @@ 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) #:nodoc: + def log_error(exception) logger = ActionView::Base.logger return unless logger diff --git a/actionview/lib/action_view/renderer/template_renderer.rb b/actionview/lib/action_view/renderer/template_renderer.rb index f40bf8f6e2..54317199de 100644 --- a/actionview/lib/action_view/renderer/template_renderer.rb +++ b/actionview/lib/action_view/renderer/template_renderer.rb @@ -44,7 +44,7 @@ module ActionView # Renders the given template. A string representing the layout can be # supplied as well. - def render_template(template, layout_name = nil, locals = nil) #:nodoc: + def render_template(template, layout_name = nil, locals = nil) view, locals = @view, locals || {} render_with_layout(layout_name, locals) do |layout| @@ -54,7 +54,7 @@ module ActionView end end - def render_with_layout(path, locals) #:nodoc: + def render_with_layout(path, locals) layout = path && find_layout(path, locals.keys, [formats.first]) content = yield(layout) diff --git a/actionview/lib/action_view/rendering.rb b/actionview/lib/action_view/rendering.rb index b70e7239fc..cf18562c45 100644 --- a/actionview/lib/action_view/rendering.rb +++ b/actionview/lib/action_view/rendering.rb @@ -91,7 +91,7 @@ module ActionView # Find and render a template based on the options given. # :api: private - def _render_template(options) #:nodoc: + def _render_template(options) variant = options.delete(:variant) assigns = options.delete(:assigns) context = view_context @@ -104,7 +104,7 @@ module ActionView end # Assign the rendered format to look up context. - def _process_format(format) #:nodoc: + def _process_format(format) super lookup_context.formats = [format.to_sym] lookup_context.rendered_format = lookup_context.formats.first @@ -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 669cffab1a..687ba7c1b4 100644 --- a/actionview/lib/action_view/routing_url_for.rb +++ b/actionview/lib/action_view/routing_url_for.rb @@ -122,18 +122,15 @@ module ActionView controller.url_options end - def _routes_context #:nodoc: - controller - end - protected :_routes_context - - def optimize_routes_generation? #:nodoc: - controller.respond_to?(:optimize_routes_generation?, true) ? - controller.optimize_routes_generation? : super - end - protected :optimize_routes_generation? - private + def _routes_context + controller + 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 2dcd6324db..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 @@ -231,11 +231,11 @@ module ActionView end end - protected + private # Compile a template. This method ensures a template is compiled # just once and removes the source after it is compiled. - def compile!(view) #:nodoc: + def compile!(view) return if @compiled # Templates can be used concurrently in threaded environments @@ -276,9 +276,8 @@ module ActionView # encode the source into <tt>Encoding.default_internal</tt>. # In general, this means that templates will be UTF-8 inside of Rails, # regardless of the original source encoding. - def compile(mod) #:nodoc: + def compile(mod) encode! - method_name = self.method_name code = @handler.call(self) # Make sure that the resulting String to be eval'd is in the @@ -309,7 +308,7 @@ module ActionView ObjectSpace.define_finalizer(self, Finalizer[method_name, mod]) end - def handle_render_error(view, e) #:nodoc: + def handle_render_error(view, e) if e.is_a?(Template::Error) e.sub_template_of(self) raise e @@ -323,17 +322,17 @@ module ActionView end end - def locals_code #:nodoc: + def locals_code # Only locals with valid variable names get set directly. Others will # still be available in local_assigns. - locals = @locals.to_set - Module::DELEGATION_RESERVED_METHOD_NAMES - locals = locals.grep(/\A(?![A-Z0-9])(?:[[:alnum:]_]|[^\0-\177])+\z/) + locals = @locals - Module::RUBY_RESERVED_KEYWORDS + 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}];" } end - def method_name #:nodoc: + def method_name @method_name ||= begin m = "_#{identifier_method_name}__#{@identifier.hash}_#{__id__}" m.tr!("-".freeze, "_".freeze) @@ -341,16 +340,14 @@ module ActionView end end - def identifier_method_name #:nodoc: + def identifier_method_name inspect.tr("^a-z_".freeze, "_".freeze) end - def instrument(action, &block) + def instrument(action, &block) # :doc: ActiveSupport::Notifications.instrument("#{action}.action_view".freeze, instrument_payload, &block) end - private - def instrument_render_template(&block) ActiveSupport::Notifications.instrument("!render_template.action_view".freeze, instrument_payload, &block) end diff --git a/actionview/lib/action_view/template/handlers/builder.rb b/actionview/lib/action_view/template/handlers/builder.rb index e08a5b5db8..e99b921cb7 100644 --- a/actionview/lib/action_view/template/handlers/builder.rb +++ b/actionview/lib/action_view/template/handlers/builder.rb @@ -7,15 +7,15 @@ 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!;" end - protected + private - def require_engine + def require_engine # :doc: @required ||= begin require "builder" true 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 ed93ebc027..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. @@ -177,7 +177,7 @@ module ActionView # always check the cache before hitting the resolver. Otherwise, # it always hits the resolver but if the key is present, check if the # resolver is fresher before returning it. - def cached(key, path_info, details, locals) #:nodoc: + def cached(key, path_info, details, locals) name, prefix, partial = path_info locals = locals.map(&:to_s).sort! @@ -191,7 +191,7 @@ module ActionView end # Ensures all the resolver information is set in the template. - def decorate(templates, path_info, details, locals) #:nodoc: + def decorate(templates, path_info, details, locals) cached = nil templates.each do |t| t.locals = locals @@ -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 88c7189d22..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" @@ -163,7 +163,7 @@ class ActionDispatch::IntegrationTest < ActiveSupport::TestCase # Stub Rails dispatcher so it does not get controller references and # simply return the controller#action as Rack::Body. class StubDispatcher < ::ActionDispatch::Routing::RouteSet::Dispatcher - protected + private def controller_reference(controller_param) controller_param end @@ -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 6152ec4720..3b314588c7 100644 --- a/actionview/test/activerecord/form_helper_activerecord_test.rb +++ b/actionview/test/activerecord/form_helper_activerecord_test.rb @@ -45,14 +45,14 @@ 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 assert_dom_equal expected, output_buffer end - protected + private def hidden_fields(method = nil) txt = %{<input name="utf8" type="hidden" value="✓" />} 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/fixtures/test/test_template_with_delegation_reserved_keywords.erb b/actionview/test/fixtures/test/test_template_with_delegation_reserved_keywords.erb new file mode 100644 index 0000000000..edfe52e422 --- /dev/null +++ b/actionview/test/fixtures/test/test_template_with_delegation_reserved_keywords.erb @@ -0,0 +1 @@ +<%= _ %> <%= arg %> <%= args %> <%= block %>
\ 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/asset_tag_helper_test.rb b/actionview/test/template/asset_tag_helper_test.rb index 3bdab42f7a..07a6452cc1 100644 --- a/actionview/test/template/asset_tag_helper_test.rb +++ b/actionview/test/template/asset_tag_helper_test.rb @@ -630,7 +630,7 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase end def test_should_return_nothing_if_asset_host_isnt_configured - assert_equal nil, compute_asset_host("foo") + assert_nil compute_asset_host("foo") end def test_should_current_request_host_is_always_returned_for_request 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/capture_helper_test.rb b/actionview/test/template/capture_helper_test.rb index 54bf9b4c33..7f37523eeb 100644 --- a/actionview/test/template/capture_helper_test.rb +++ b/actionview/test/template/capture_helper_test.rb @@ -127,18 +127,18 @@ class CaptureHelperTest < ActionView::TestCase def test_content_for_returns_nil_when_writing assert ! content_for?(:title) - assert_equal nil, content_for(:title, "foo") - assert_equal nil, content_for(:title) { output_buffer << "bar"; nil } - assert_equal nil, content_for(:title) { output_buffer << " \n "; nil } + assert_nil content_for(:title, "foo") + assert_nil content_for(:title) { output_buffer << "bar"; nil } + assert_nil content_for(:title) { output_buffer << " \n "; nil } assert_equal "foobar", content_for(:title) - assert_equal nil, content_for(:title, "foo", flush: true) - assert_equal nil, content_for(:title, flush: true) { output_buffer << "bar"; nil } - assert_equal nil, content_for(:title, flush: true) { output_buffer << " \n "; nil } + assert_nil content_for(:title, "foo", flush: true) + assert_nil content_for(:title, flush: true) { output_buffer << "bar"; nil } + assert_nil content_for(:title, flush: true) { output_buffer << " \n "; nil } assert_equal "bar", content_for(:title) end def test_content_for_returns_nil_when_content_missing - assert_equal nil, content_for(:some_missing_key) + assert_nil content_for(:some_missing_key) end def test_content_for_question_mark diff --git a/actionview/test/template/compiled_templates_test.rb b/actionview/test/template/compiled_templates_test.rb index 3ecac46d34..adb2be9be4 100644 --- a/actionview/test/template/compiled_templates_test.rb +++ b/actionview/test/template/compiled_templates_test.rb @@ -24,10 +24,24 @@ class CompiledTemplatesTest < ActiveSupport::TestCase assert_equal locals.inspect, render(file: "test/render_file_inspect_local_assigns", locals: locals) end + def test_template_with_delegation_reserved_keywords + locals = { + _: "one", + arg: "two", + args: "three", + block: "four", + } + assert_equal "one two three four", render(file: "test/test_template_with_delegation_reserved_keywords", locals: locals) + end + def test_template_with_unicode_identifier 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/date_helper_test.rb b/actionview/test/template/date_helper_test.rb index a84ac18bee..bfd3ecd6fd 100644 --- a/actionview/test/template/date_helper_test.rb +++ b/actionview/test/template/date_helper_test.rb @@ -1407,7 +1407,6 @@ class DateHelperTest < ActionView::TestCase end def test_select_datetime_with_custom_prompt - expected = %(<select id="date_first_year" name="date[first][year]">\n) expected << %(<option value="">Choose year</option>\n<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) expected << "</select>\n" 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 c80a2f61b9..a4a2966ff9 100644 --- a/actionview/test/template/form_helper/form_with_test.rb +++ b/actionview/test/template/form_helper/form_with_test.rb @@ -116,6 +116,16 @@ class FormWithActsLikeFormTagTest < FormWithTest assert_dom_equal expected, output_buffer end + + def test_form_with_with_block_in_erb_and_local_true + output_buffer = render_erb("<%= form_with(url: 'http://www.example.com', local: true) do %>Hello world!<% end %>") + + expected = whole_form("http://www.example.com", local: true) do + "Hello world!" + end + + assert_dom_equal expected, output_buffer + end end class FormWithActsLikeFormForTest < FormWithTest @@ -300,19 +310,88 @@ 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' 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' 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 assert_dom_equal expected, output_buffer end + def test_form_with_only_url_on_create + form_with(url: "/posts") do |f| + concat f.label :title, "Label me" + concat f.text_field :title + end + + expected = whole_form("/posts") do + '<label for="title">Label me</label>' \ + '<input type="text" name="title">' + end + + assert_dom_equal expected, output_buffer + end + + def test_form_with_only_url_on_update + form_with(url: "/posts/123") do |f| + concat f.label :title, "Label me" + concat f.text_field :title + end + + expected = whole_form("/posts/123") do + '<label for="title">Label me</label>' \ + '<input type="text" name="title">' + end + + assert_dom_equal expected, output_buffer + end + + def test_form_with_general_attributes + form_with(url: "/posts/123") do |f| + concat f.text_field :no_model_to_back_this_badboy + end + + expected = whole_form("/posts/123") do + '<input type="text" name="no_model_to_back_this_badboy">' + end + + assert_dom_equal expected, output_buffer + end + + def test_form_with_attribute_not_on_model + form_with(model: @post) do |f| + concat f.text_field :this_dont_exist_on_post + end + + expected = whole_form("/posts/123", method: :patch) do + '<input type="text" name="post[this_dont_exist_on_post]">' + end + + assert_dom_equal expected, output_buffer + end + + def test_form_with_doesnt_call_private_or_protected_properties_on_form_object_skipping_value + obj = Class.new do + private def private_property + "That would be great." + end + + protected def protected_property + "I believe you have my stapler." + end + end.new + + form_with(model: obj, scope: "other_name", url: "/", id: "edit-other-name") do |f| + assert_dom_equal '<input type="hidden" name="other_name[private_property]">', f.hidden_field(:private_property) + assert_dom_equal '<input type="hidden" name="other_name[protected_property]">', f.hidden_field(:protected_property) + end + end + def test_form_with_with_collection_radio_buttons post = Post.new def post.active; false; end @@ -321,10 +400,10 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts") 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 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 @@ -343,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 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 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 @@ -369,14 +448,14 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts") 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 id='post_id' name='post[id]' type='hidden' value='1' />" + "<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 assert_dom_equal expected, output_buffer @@ -391,10 +470,10 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts") 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 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 @@ -410,12 +489,12 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts") 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' 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 @@ -434,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' 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' 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 @@ -464,17 +543,17 @@ 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' 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' />" + "<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 assert_dom_equal expected, output_buffer @@ -490,8 +569,8 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts") 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' name='post[1][tag_ids][]' type='checkbox' value='1' />" \ "<label for='post_1_tag_ids_1'>Tag 1</label>" end @@ -499,22 +578,18 @@ class FormWithActsLikeFormForTest < FormWithTest end def test_form_with_with_file_field_generate_multipart - Post.send :attr_accessor, :file - form_with(model: @post, id: "create-post") do |f| concat f.file_field(:file) end expected = whole_form("/posts/123", "create-post", method: "patch", multipart: true) do - "<input name='post[file]' type='file' id='post_file' />" + "<input name='post[file]' type='file' />" end assert_dom_equal expected, output_buffer end def test_fields_with_file_field_generate_multipart - Comment.send :attr_accessor, :file - form_with(model: @post) do |f| concat f.fields(:comment, model: @post) { |c| concat c.file_field(:file) @@ -522,7 +597,7 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch", multipart: true) do - "<input name='post[comment][file]' type='file' id='post_comment_file' />" + "<input name='post[comment][file]' type='file' />" end assert_dom_equal expected, output_buffer @@ -561,7 +636,7 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/44", 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' value='And his name will be forty and four.' />" \ "<input name='commit' data-disable-with='Edit post' type='submit' value='Edit post' />" end @@ -578,29 +653,17 @@ 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]' 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]' 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 assert_dom_equal expected, output_buffer end - def test_form_tags_do_not_call_private_properties_on_form_object - obj = Class.new do - private def private_property - raise "This method should not be called." - end - end.new - - form_with(model: obj, scope: "other_name", url: "/", id: "edit-other-name") do |f| - assert_raise(NoMethodError) { f.hidden_field(:private_property) } - end - end - def test_form_with_with_method_as_part_of_html_options form_with(model: @post, url: "/", id: "create-post", html: { method: :delete }) do |f| concat f.text_field(:title) @@ -609,10 +672,10 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/", "create-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[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" + "<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 assert_dom_equal expected, output_buffer @@ -626,10 +689,10 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/", "create-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[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" + "<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 assert_dom_equal expected, output_buffer @@ -643,7 +706,7 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/search", "search-post", method: "get") do - "<input name='post[title]' type='search' id='post_title' />" + "<input name='post[title]' type='search' />" end assert_dom_equal expected, output_buffer @@ -657,10 +720,10 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/", "create-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[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" + "<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 assert_dom_equal expected, output_buffer @@ -672,7 +735,7 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/", skip_enforcing_utf8: true) do - "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + "<input name='post[title]' type='text' value='Hello World' />" end assert_dom_equal expected, output_buffer @@ -684,7 +747,7 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/", skip_enforcing_utf8: false) do - "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + "<input name='post[title]' type='text' value='Hello World' />" end assert_dom_equal expected, output_buffer @@ -698,10 +761,10 @@ class FormWithActsLikeFormForTest < FormWithTest 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[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" + "<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 assert_dom_equal expected, output_buffer @@ -716,11 +779,11 @@ 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' 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' />" + "<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 assert_dom_equal expected, output_buffer @@ -734,10 +797,10 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", 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[][secret]' checked='checked' type='checkbox' id='post__secret' value='1' />" + "<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 assert_dom_equal expected, output_buffer @@ -751,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' 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' value='' /></div>" \ "<input name='commit' data-disable-with='Create post' type='submit' value='Create post' />" end @@ -769,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' 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' value='' /></div>" \ "<input name='commit' data-disable-with='Create post' type='submit' value='Create post' />" end @@ -784,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 @@ -800,10 +863,10 @@ class FormWithActsLikeFormForTest < FormWithTest 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[secret]' checked='checked' type='checkbox' id='namespace_post_secret' value='1' />" + "<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 assert_dom_equal expected, output_buffer @@ -868,6 +931,40 @@ class FormWithActsLikeFormForTest < FormWithTest end end + def test_fields_with_attributes_not_on_model + form_with(model: @post) do |f| + concat f.fields(:comment) { |c| + concat c.text_field :dont_exist_on_model + } + end + + expected = whole_form("/posts/123", method: :patch) do + '<input type="text" name="post[comment][dont_exist_on_model]">' + end + + assert_dom_equal expected, output_buffer + end + + def test_fields_with_attributes_not_on_model_deep_nested + @comment.save + form_with(scope: :posts) do |f| + f.fields("post[]", model: @post) do |f2| + f2.text_field(:id) + @post.comments.each do |comment| + concat f2.fields("comment[]", model: comment) { |c| + concat c.text_field(:dont_exist_on_model) + } + end + end + end + + expected = whole_form do + '<input name="posts[post][0][comment][1][dont_exist_on_model]" type="text">' + end + + assert_dom_equal expected, output_buffer + end + def test_nested_fields @comment.body = "Hello World" form_with(model: @post) do |f| @@ -877,7 +974,7 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - "<input name='post[comment][body]' type='text' id='post_comment_body' value='Hello World' />" + "<input name='post[comment][body]' type='text' value='Hello World' />" end assert_dom_equal expected, output_buffer @@ -897,7 +994,7 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form do - "<input name='posts[post][0][comment][1][name]' type='text' id='posts_post_0_comment_1_name' value='comment #1' />" + "<input name='posts[post][0][comment][1][name]' type='text' value='comment #1' />" end assert_dom_equal expected, output_buffer @@ -912,8 +1009,8 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", 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' value='Hello World' />" \ + "<input name='post[123][comment][][name]' type='text' value='new comment' />" end assert_dom_equal expected, output_buffer @@ -928,8 +1025,8 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - "<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' />" + "<input name='post[1][title]' type='text' value='Hello World' />" \ + "<input name='post[1][comment][1][name]' type='text' value='new comment' />" end assert_dom_equal expected, output_buffer @@ -943,7 +1040,7 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - "<input name='post[1][comment][title]' type='text' id='post_1_comment_title' value='Hello World' />" + "<input name='post[1][comment][title]' type='text' value='Hello World' />" end assert_dom_equal expected, output_buffer @@ -957,7 +1054,7 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - "<input name='post[1][comment][5][title]' type='text' id='post_1_comment_5_title' value='Hello World' />" + "<input name='post[1][comment][5][title]' type='text' value='Hello World' />" end assert_dom_equal expected, output_buffer @@ -971,7 +1068,7 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - "<input name='post[123][comment][title]' type='text' id='post_123_comment_title' value='Hello World' />" + "<input name='post[123][comment][title]' type='text' value='Hello World' />" end assert_dom_equal expected, output_buffer @@ -985,7 +1082,7 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - "<input name='post[comment][5][title]' type='radio' id='post_comment_5_title_hello' value='hello' />" + "<input name='post[comment][5][title]' type='radio' value='hello' />" end assert_dom_equal expected, output_buffer @@ -999,7 +1096,7 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - "<input name='post[123][comment][123][title]' type='text' id='post_123_comment_123_title' value='Hello World' />" + "<input name='post[123][comment][123][title]' type='text' value='Hello World' />" end assert_dom_equal expected, output_buffer @@ -1019,9 +1116,9 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - "<input name='post[123][comment][5][title]' type='text' id='post_123_comment_5_title' value='Hello World' />" + "<input name='post[123][comment][5][title]' type='text' value='Hello World' />" end + whole_form("/posts/123", method: "patch") do - "<input name='post[1][comment][123][title]' type='text' id='post_1_comment_123_title' value='Hello World' />" + "<input name='post[1][comment][123][title]' type='text' value='Hello World' />" end assert_dom_equal expected, output_buffer @@ -1038,8 +1135,8 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", 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="new author" />' + '<input name="post[title]" type="text" value="Hello World" />' \ + '<input name="post[author_attributes][name]" type="text" value="new author" />' end assert_dom_equal expected, output_buffer @@ -1065,9 +1162,9 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", 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 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 assert_dom_equal expected, output_buffer @@ -1084,9 +1181,9 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", 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 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 assert_dom_equal expected, output_buffer @@ -1103,8 +1200,8 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", 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" value="Hello World" />' \ + '<input name="post[author_attributes][name]" type="text" value="author #321" />' end assert_dom_equal expected, output_buffer @@ -1121,8 +1218,8 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", 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" value="Hello World" />' \ + '<input name="post[author_attributes][name]" type="text" value="author #321" />' end assert_dom_equal expected, output_buffer @@ -1139,9 +1236,9 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", 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 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 assert_dom_equal expected, output_buffer @@ -1159,9 +1256,9 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", 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 id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #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 assert_dom_equal expected, output_buffer @@ -1180,11 +1277,11 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", 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 id="post_comments_attributes_1_id" 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][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 assert_dom_equal expected, output_buffer @@ -1207,11 +1304,11 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", 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 id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />' + '<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 assert_dom_equal expected, output_buffer @@ -1234,10 +1331,10 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", 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 id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />' + '<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 assert_dom_equal expected, output_buffer @@ -1260,11 +1357,11 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", 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 id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />' + '<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 assert_dom_equal expected, output_buffer @@ -1283,11 +1380,11 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", 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 id="post_comments_attributes_1_id" 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][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 assert_dom_equal expected, output_buffer @@ -1307,11 +1404,11 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", 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 id="post_comments_attributes_1_name" 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][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 assert_dom_equal expected, output_buffer @@ -1330,9 +1427,9 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", 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 id="post_comments_attributes_1_name" name="post[comments_attributes][1][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 assert_dom_equal expected, output_buffer @@ -1351,10 +1448,10 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", 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 id="post_comments_attributes_1_name" name="post[comments_attributes][1][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="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 assert_dom_equal expected, output_buffer @@ -1369,7 +1466,7 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + '<input name="post[title]" type="text" value="Hello World" />' end assert_dom_equal expected, output_buffer @@ -1386,11 +1483,11 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", 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 id="post_comments_attributes_1_id" 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][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 assert_dom_equal expected, output_buffer @@ -1407,11 +1504,11 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", 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 id="post_comments_attributes_1_id" 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][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 assert_dom_equal expected, output_buffer @@ -1442,11 +1539,11 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", 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 id="post_comments_attributes_1_id" 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][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 assert_dom_equal expected, output_buffer @@ -1465,10 +1562,10 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", 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 id="post_comments_attributes_1_name" name="post[comments_attributes][1][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="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 assert_dom_equal expected, output_buffer @@ -1485,8 +1582,8 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", 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_id" name="post[comments_attributes][abc][id]" type="hidden" value="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 assert_dom_equal expected, output_buffer @@ -1502,8 +1599,8 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", 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_id" name="post[comments_attributes][abc][id]" type="hidden" value="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 assert_dom_equal expected, output_buffer @@ -1525,8 +1622,8 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", 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_id" name="post[comments_attributes][abc][id]" type="hidden" value="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 assert_dom_equal expected, output_buffer @@ -1611,18 +1708,18 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", 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_tags_attributes_1_id" name="post[tags_attributes][1][id]" type="hidden" value="456" />' + '<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 assert_dom_equal expected, output_buffer @@ -1638,7 +1735,7 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="hash backed author" />' + '<input name="post[author_attributes][name]" type="text" value="hash backed author" />' end assert_dom_equal expected, output_buffer @@ -1652,10 +1749,10 @@ class FormWithActsLikeFormForTest < FormWithTest 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[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" + "<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 end @@ -1668,10 +1765,10 @@ class FormWithActsLikeFormForTest < FormWithTest 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][secret]' checked='checked' type='checkbox' id='post_123_secret' value='1' />" + "<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 end @@ -1684,10 +1781,10 @@ class FormWithActsLikeFormForTest < FormWithTest 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[][secret]' checked='checked' type='checkbox' id='post__secret' value='1' />" + "<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 end @@ -1700,10 +1797,10 @@ class FormWithActsLikeFormForTest < FormWithTest 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][secret]' checked='checked' type='checkbox' id='post_abc_secret' value='1' />" + "<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 end @@ -1716,10 +1813,10 @@ class FormWithActsLikeFormForTest < FormWithTest 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[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" + "<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 end @@ -1732,10 +1829,10 @@ class FormWithActsLikeFormForTest < FormWithTest 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[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" + "<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 end @@ -1746,8 +1843,8 @@ class FormWithActsLikeFormForTest < FormWithTest concat f.text_field(:title) end - assert_dom_equal "<label for=\"author_post_title\">Title</label>" + - "<input name='author[post][title]' type='text' id='author_post_title' value='Hello World' />", + assert_dom_equal "<label for=\"author_post_title\">Title</label>" \ + "<input name='author[post][title]' type='text' value='Hello World' />", output_buffer end @@ -1757,8 +1854,8 @@ class FormWithActsLikeFormForTest < FormWithTest concat f.text_field(:title) end - 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' />", + 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 @@ -1777,10 +1874,10 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", "create-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='parent_post[secret]' checked='checked' type='checkbox' id='parent_post_secret' value='1' />" + "<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 assert_dom_equal expected, output_buffer @@ -1797,9 +1894,9 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", "create-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[comment][name]' type='text' id='post_comment_name' value='new comment' />" + "<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 assert_dom_equal expected, output_buffer @@ -1813,7 +1910,7 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - "<input name='post[category][name]' type='text' id='post_category_name' />" + "<input name='post[category][name]' type='text' />" end assert_dom_equal expected, output_buffer @@ -1837,9 +1934,9 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", 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='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/>" + "<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 assert_dom_equal expected, output_buffer @@ -1856,9 +1953,9 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", 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='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/>" + "<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 assert_dom_equal expected, output_buffer @@ -1875,7 +1972,7 @@ class FormWithActsLikeFormForTest < FormWithTest end expected = whole_form("/posts/123", method: "patch") do - "<label for='title'>Title:</label> <input name='post[title]' type='text' id='post_title' value='Hello World' /><br/>" + "<label for='title'>Title:</label> <input name='post[title]' type='text' value='Hello World' /><br/>" end assert_dom_equal expected, output_buffer @@ -1890,7 +1987,7 @@ class FormWithActsLikeFormForTest < FormWithTest concat f.text_field(:title) end - expected = "<label for='title'>Title:</label> <input name='post[title]' type='text' id='post_title' value='Hello World' /><br/>" + expected = "<label for='title'>Title:</label> <input name='post[title]' type='text' value='Hello World' /><br/>" assert_dom_equal expected, output_buffer end @@ -1902,7 +1999,7 @@ class FormWithActsLikeFormForTest < FormWithTest concat f.text_field(:title) end - expected = "<label for='title'>Title:</label> <input name='post[title]' type='text' id='post_title' value='Hello World' /><br/>" + expected = "<label for='title'>Title:</label> <input name='post[title]' type='text' value='Hello World' /><br/>" assert_dom_equal expected, output_buffer end @@ -1915,9 +2012,9 @@ class FormWithActsLikeFormForTest < FormWithTest 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='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/>" + "<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 end @@ -2086,7 +2183,7 @@ class FormWithActsLikeFormForTest < FormWithTest assert_equal 1, initialization_count, "form builder instantiated more than once" end - protected + private def hidden_fields(options = {}) method = options[:method] diff --git a/actionview/test/template/form_helper_test.rb b/actionview/test/template/form_helper_test.rb index 022bf315ce..490df5e4d9 100644 --- a/actionview/test/template/form_helper_test.rb +++ b/actionview/test/template/form_helper_test.rb @@ -767,7 +767,6 @@ class FormHelperTest < ActionView::TestCase '<input name="post[bar][comment_ids][]" type="hidden" value="0" /><input checked="checked" id="post_bar_comment_ids_3" name="post[bar][comment_ids][]" type="checkbox" value="3" />', check_box("post", "comment_ids", { multiple: true, index: "bar" }, 3) ) - end def test_checkbox_disabled_disables_hidden_field @@ -1515,13 +1514,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 +1535,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 +1557,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 +1583,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 +1605,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 +1624,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 +1643,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 +1667,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 +1697,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 +1723,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 +1741,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 @@ -1751,8 +1750,6 @@ class FormHelperTest < ActionView::TestCase end def test_form_for_with_file_field_generate_multipart - Post.send :attr_accessor, :file - form_for(@post, html: { id: "create-post" }) do |f| concat f.file_field(:file) end @@ -1765,8 +1762,6 @@ class FormHelperTest < ActionView::TestCase end def test_fields_for_with_file_field_generate_multipart - Comment.send :attr_accessor, :file - form_for(@post) do |f| concat f.fields_for(:comment, @post) { |c| concat c.file_field(:file) @@ -1801,7 +1796,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 @@ -1818,11 +1813,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 @@ -1833,9 +1828,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| @@ -1851,9 +1846,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 @@ -1868,9 +1863,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 @@ -1899,9 +1894,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 @@ -1940,9 +1935,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 @@ -1959,9 +1954,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 @@ -1977,9 +1972,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 @@ -1995,10 +1990,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 @@ -2013,9 +2008,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 @@ -2030,8 +2025,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 @@ -2048,8 +2043,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 @@ -2063,7 +2058,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 @@ -2078,9 +2073,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 @@ -2102,7 +2097,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 @@ -2128,7 +2123,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 @@ -2140,7 +2135,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 @@ -2158,8 +2153,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 @@ -2266,11 +2261,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 @@ -2285,7 +2282,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 @@ -2395,7 +2392,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 @@ -2422,8 +2419,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 @@ -2441,8 +2438,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 @@ -2460,7 +2457,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 @@ -2478,7 +2475,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 @@ -2496,8 +2493,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 @@ -2516,8 +2513,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 @@ -2537,10 +2534,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 @@ -2564,10 +2561,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 @@ -2591,9 +2588,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 @@ -2617,10 +2614,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 @@ -2640,10 +2637,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 @@ -2664,10 +2661,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 @@ -2687,8 +2684,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 @@ -2708,9 +2705,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 @@ -2743,10 +2740,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 @@ -2764,10 +2761,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 @@ -2799,10 +2796,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 @@ -2822,9 +2819,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 @@ -2842,7 +2839,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 @@ -2859,7 +2856,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 @@ -2882,7 +2879,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 @@ -2968,17 +2965,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 @@ -3009,9 +3006,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 @@ -3025,9 +3022,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 @@ -3041,9 +3038,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 @@ -3057,9 +3054,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 @@ -3073,9 +3070,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 @@ -3089,9 +3086,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 @@ -3103,7 +3100,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 @@ -3114,7 +3111,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 @@ -3134,9 +3131,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 @@ -3154,8 +3151,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 @@ -3194,8 +3191,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 @@ -3213,8 +3210,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 @@ -3272,8 +3269,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 @@ -3443,7 +3440,7 @@ class FormHelperTest < ActionView::TestCase assert_equal 1, initialization_count, "form builder instantiated more than once" end - protected + private def hidden_fields(options = {}) method = options[:method] 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/number_helper_test.rb b/actionview/test/template/number_helper_test.rb index 2a2ada2b36..678120a9c9 100644 --- a/actionview/test/template/number_helper_test.rb +++ b/actionview/test/template/number_helper_test.rb @@ -4,7 +4,7 @@ class NumberHelperTest < ActionView::TestCase tests ActionView::Helpers::NumberHelper def test_number_to_phone - assert_equal nil, number_to_phone(nil) + assert_nil number_to_phone(nil) assert_equal "555-1234", number_to_phone(5551234) assert_equal "(800) 555-1212 x 123", number_to_phone(8005551212, area_code: true, extension: 123) assert_equal "+18005551212", number_to_phone(8005551212, country_code: 1, delimiter: "") @@ -13,7 +13,7 @@ class NumberHelperTest < ActionView::TestCase end def test_number_to_currency - assert_equal nil, number_to_currency(nil) + assert_nil number_to_currency(nil) assert_equal "$1,234,567,890.50", number_to_currency(1234567890.50) assert_equal "$1,234,567,892", number_to_currency(1234567891.50, precision: 0) assert_equal "1,234,567,890.50 - Kč", number_to_currency("-1234567890.50", unit: raw("Kč"), format: "%n %u", negative_format: "%n - %u") @@ -25,7 +25,7 @@ class NumberHelperTest < ActionView::TestCase end def test_number_to_percentage - assert_equal nil, number_to_percentage(nil) + assert_nil number_to_percentage(nil) assert_equal "100.000%", number_to_percentage(100) assert_equal "100.000 %", number_to_percentage(100, format: "%n %") assert_equal "<b>100.000</b> %", number_to_percentage(100, format: "<b>%n</b> %") @@ -43,13 +43,13 @@ class NumberHelperTest < ActionView::TestCase end def test_number_with_delimiter - assert_equal nil, number_with_delimiter(nil) + assert_nil number_with_delimiter(nil) assert_equal "12,345,678", number_with_delimiter(12345678) assert_equal "0", number_with_delimiter(0) end def test_number_with_precision - assert_equal nil, number_with_precision(nil) + assert_nil number_with_precision(nil) assert_equal "-111.235", number_with_precision(-111.2346) assert_equal "111.00", number_with_precision(111, precision: 2) assert_equal "0.00100", number_with_precision(0.001, precision: 5) @@ -57,13 +57,13 @@ class NumberHelperTest < ActionView::TestCase end def test_number_to_human_size - assert_equal nil, number_to_human_size(nil) + assert_nil number_to_human_size(nil) assert_equal "3 Bytes", number_to_human_size(3.14159265) assert_equal "1.2 MB", number_to_human_size(1234567, precision: 2) end def test_number_to_human - assert_equal nil, number_to_human(nil) + assert_nil number_to_human(nil) assert_equal "0", number_to_human(0) assert_equal "1.23 Thousand", number_to_human(1234) assert_equal "489.0 Thousand", number_to_human(489000, precision: 4, strip_insignificant_zeros: false) 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..412948719c 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,13 +402,11 @@ 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 - # TODO: The reason for this test is unclear, improve documentation - def test_render_missing_xml_partial_and_raise_missing_template + def test_render_partial_with_non_existent_format_and_raise_missing_template @view.formats = [:xml] assert_raises(ActionView::MissingTemplate) { @view.render(partial: "test/layout_for_partial") } ensure @@ -424,7 +431,7 @@ module RenderTestCases end CustomHandler = lambda do |template| - "@output_buffer = ''\n" + + "@output_buffer = ''\n" \ "@output_buffer << 'source: #{template.source.inspect}'\n" end @@ -475,11 +482,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/test_case_test.rb b/actionview/test/template/test_case_test.rb index 41225000f0..3deddd5706 100644 --- a/actionview/test/template/test_case_test.rb +++ b/actionview/test/template/test_case_test.rb @@ -50,7 +50,7 @@ module ActionView end test "retrieve non existing config values" do - assert_equal nil, ActionView::Base.new.config.something_odd + assert_nil ActionView::Base.new.config.something_odd end test "works without testing a helper module" do 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 5a2319fe96..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") @@ -810,9 +819,9 @@ class TasksController < ActionController::Base render_default end - protected + 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 3953116061..41ff76135e 100644 --- a/activejob/Rakefile +++ b/activejob/Rakefile @@ -1,8 +1,9 @@ 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) -ACTIVEJOB_ADAPTERS -= %w(queue_classic) if defined?(JRUBY_VERSION) +#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 task test: "test:default" 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/enqueuing.rb b/activejob/lib/active_job/enqueuing.rb index 78eca1d2a1..c73117e7f3 100644 --- a/activejob/lib/active_job/enqueuing.rb +++ b/activejob/lib/active_job/enqueuing.rb @@ -18,8 +18,8 @@ module ActiveJob job_or_instantiate(*args).enqueue end - protected - def job_or_instantiate(*args) + private + def job_or_instantiate(*args) # :doc: args.first.is_a?(self) ? args.first : new(*args) end end 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 e6334d14ea..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 @@ -315,34 +342,46 @@ module ActiveJob end private - def clear_enqueued_jobs # :nodoc: + def clear_enqueued_jobs enqueued_jobs.clear end - def clear_performed_jobs # :nodoc: + def clear_performed_jobs performed_jobs.clear end - def enqueued_jobs_size(only: nil) # :nodoc: - 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 - def serialize_args_for_assertion(args) # :nodoc: + def serialize_args_for_assertion(args) args.dup.tap do |serialized_args| serialized_args[:args] = ActiveJob::Arguments.serialize(serialized_args[:args]) if serialized_args[:args] serialized_args[:at] = serialized_args[:at].to_f if serialized_args[:at] end end - def instantiate_job(payload) # :nodoc: + def instantiate_job(payload) job = payload[:job].new(*payload[:args]) job.scheduled_at = Time.at(payload[:at]) if payload.key?(:at) 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 ec78a402d7..171fb1e593 100644 --- a/activejob/test/cases/queue_priority_test.rb +++ b/activejob/test/cases/queue_priority_test.rb @@ -3,7 +3,7 @@ require "jobs/hello_job" class QueuePriorityTest < ActiveSupport::TestCase test "priority unset by default" do - assert_equal nil, HelloJob.priority + assert_nil HelloJob.priority end test "uses given priority" do @@ -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 685c93da2d..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 @@ -256,11 +288,11 @@ end class PerformedJobsTest < ActiveJob::TestCase def test_performed_enqueue_jobs_with_only_option_doesnt_leak_outside_the_block - assert_equal nil, queue_adapter.filter + assert_nil queue_adapter.filter perform_enqueued_jobs only: HelloJob do assert_equal HelloJob, queue_adapter.filter end - assert_equal nil, queue_adapter.filter + assert_nil queue_adapter.filter end def test_assert_performed_jobs @@ -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/helper.rb b/activejob/test/helper.rb index 758506b3c0..db07ecab7a 100644 --- a/activejob/test/helper.rb +++ b/activejob/test/helper.rb @@ -1,10 +1,10 @@ require "active_job" require "support/job_buffer" -ActiveSupport.halt_callback_chains_on_return_false = false GlobalID.app = "aj" @adapter = ENV["AJ_ADAPTER"] || "inline" +puts "Using #{@adapter}" if ENV["AJ_INTEGRATION_TESTS"] require "support/integration/helper" 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/integration/adapters/sneakers.rb b/activejob/test/support/integration/adapters/sneakers.rb index 911f70407e..1c8dfaca59 100644 --- a/activejob/test/support/integration/adapters/sneakers.rb +++ b/activejob/test/support/integration/adapters/sneakers.rb @@ -73,7 +73,7 @@ module SneakersJobsManager true end - protected + private def bunny_publisher @bunny_publisher ||= begin p = ActiveJob::QueueAdapters::SneakersAdapter::JobWrapper.send(:publisher) diff --git a/activejob/test/support/integration/test_case_helpers.rb b/activejob/test/support/integration/test_case_helpers.rb index 3357489f20..41bf9c89d1 100644 --- a/activejob/test/support/integration/test_case_helpers.rb +++ b/activejob/test/support/integration/test_case_helpers.rb @@ -17,7 +17,7 @@ module TestCaseHelpers end end - protected + private def jobs_manager JobsManager.current_manager 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 9c53e2ecec..edaac8c7cd 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -1,3 +1,15 @@ +* Remove deprecated behavior that halts callbacks when the return is false. + + *Rafael Mendonça França* + +* Remove unused `ActiveModel::TestCase` class. + + *Yuji Yaginuma* + +* Moved DecimalWithoutScale, Text, and UnsignedInteger from Active Model to Active Record + + *Iain Beeston* + * Allow indifferent access in `ActiveModel::Errors`. `#include?`, `#has_key?`, `#key?`, `#delete` and `#full_messages_for`. 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/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb index 1441b146f8..09825cf861 100644 --- a/activemodel/lib/active_model/attribute_methods.rb +++ b/activemodel/lib/active_model/attribute_methods.rb @@ -334,12 +334,11 @@ module ActiveModel }.tap { |mod| include mod } end - protected - def instance_method_already_implemented?(method_name) #:nodoc: + private + def instance_method_already_implemented?(method_name) generated_attribute_methods.method_defined?(method_name) end - private # The methods +method_missing+ and +respond_to?+ of this module are # invoked often in a typical rails, both of which invoke the method # +matched_attribute_method+. The latter method iterates through an @@ -349,11 +348,11 @@ module ActiveModel # used to alleviate the GC, which ultimately also speeds up the app # significantly (in our case our test suite finishes 10% faster with # this cache). - def attribute_method_matchers_cache #:nodoc: + def attribute_method_matchers_cache @attribute_method_matchers_cache ||= Concurrent::Map.new(initial_capacity: 4) end - def attribute_method_matchers_matching(method_name) #:nodoc: + def attribute_method_matchers_matching(method_name) attribute_method_matchers_cache.compute_if_absent(method_name) do # Must try to match prefixes/suffixes first, or else the matcher with no prefix/suffix # will match every time. @@ -365,7 +364,7 @@ module ActiveModel # Define a method `name` in `mod` that dispatches to `send` # using the given `extra` args. This falls back on `define_method` # and `send` if the given names cannot be compiled. - def define_proxy_call(include_private, mod, name, send, *extra) #:nodoc: + def define_proxy_call(include_private, mod, name, send, *extra) defn = if NAME_COMPILABLE_REGEXP.match?(name) "def #{name}(*args)" else @@ -458,12 +457,11 @@ module ActiveModel end end - protected - def attribute_method?(attr_name) #:nodoc: + private + def attribute_method?(attr_name) respond_to_without_attributes?(:attributes) && attributes.include?(attr_name) end - private # Returns a struct representing the matching attribute method. # The struct's attributes are prefix, base and suffix. def matched_attribute_method(method_name) diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb index 283090e380..eac2761433 100644 --- a/activemodel/lib/active_model/callbacks.rb +++ b/activemodel/lib/active_model/callbacks.rb @@ -103,7 +103,6 @@ module ActiveModel def define_model_callbacks(*callbacks) options = callbacks.extract_options! options = { - terminator: deprecated_false_terminator, skip_after_callbacks_if_terminated: true, scope: [:kind, :name], only: [:before, :around, :after] @@ -122,19 +121,19 @@ module ActiveModel private - def _define_before_model_callback(klass, callback) #:nodoc: + def _define_before_model_callback(klass, callback) klass.define_singleton_method("before_#{callback}") do |*args, &block| set_callback(:"#{callback}", :before, *args, &block) end end - def _define_around_model_callback(klass, callback) #:nodoc: + def _define_around_model_callback(klass, callback) klass.define_singleton_method("around_#{callback}") do |*args, &block| set_callback(:"#{callback}", :around, *args, &block) end end - def _define_after_model_callback(klass, callback) #:nodoc: + def _define_after_model_callback(klass, callback) klass.define_singleton_method("after_#{callback}") do |*args, &block| options = args.extract_options! options[:prepend] = true diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index 5ee9413cff..9df4ca51fe 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -419,16 +419,23 @@ module ActiveModel I18n.translate(key, options) end - def marshal_dump + def marshal_dump # :nodoc: [@base, without_default_proc(@messages), without_default_proc(@details)] end - def marshal_load(array) + def marshal_load(array) # :nodoc: @base, @messages, @details = array apply_default_array(@messages) apply_default_array(@details) end + def init_with(coder) # :nodoc: + coder.map.each { |k, v| instance_variable_set(:"@#{k}", v) } + @details ||= {} + apply_default_array(@messages) + apply_default_array(@details) + end + private def normalize_message(attribute, message, options) case message diff --git a/activemodel/lib/active_model/forbidden_attributes_protection.rb b/activemodel/lib/active_model/forbidden_attributes_protection.rb index d2c6a89cc2..45ab8a2ca1 100644 --- a/activemodel/lib/active_model/forbidden_attributes_protection.rb +++ b/activemodel/lib/active_model/forbidden_attributes_protection.rb @@ -15,7 +15,7 @@ module ActiveModel end module ForbiddenAttributesProtection # :nodoc: - protected + private def sanitize_for_mass_assignment(attributes) if attributes.respond_to?(:permitted?) raise ActiveModel::ForbiddenAttributesError if !attributes.permitted? diff --git a/activemodel/lib/active_model/model.rb b/activemodel/lib/active_model/model.rb index e683050787..945a5402a3 100644 --- a/activemodel/lib/active_model/model.rb +++ b/activemodel/lib/active_model/model.rb @@ -2,10 +2,10 @@ module ActiveModel # == Active \Model \Basic \Model # # Includes the required interface for an object to interact with - # <tt>ActionPack</tt>, using different <tt>ActiveModel</tt> modules. + # Action Pack and Action View, using different Active Model modules. # It includes model name introspections, conversions, translations and # validations. Besides that, it allows you to initialize the object with a - # hash of attributes, pretty much like <tt>ActiveRecord</tt> does. + # hash of attributes, pretty much like Active Record does. # # A minimal implementation could be: # 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.rb b/activemodel/lib/active_model/type.rb index 0d3349e236..b8e6d2376b 100644 --- a/activemodel/lib/active_model/type.rb +++ b/activemodel/lib/active_model/type.rb @@ -7,14 +7,11 @@ require "active_model/type/boolean" require "active_model/type/date" require "active_model/type/date_time" require "active_model/type/decimal" -require "active_model/type/decimal_without_scale" require "active_model/type/float" require "active_model/type/immutable_string" require "active_model/type/integer" require "active_model/type/string" -require "active_model/type/text" require "active_model/type/time" -require "active_model/type/unsigned_integer" require "active_model/type/registry" @@ -53,7 +50,6 @@ module ActiveModel register(:immutable_string, Type::ImmutableString) register(:integer, Type::Integer) register(:string, Type::String) - register(:text, Type::Text) register(:time, Type::Time) end end 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/type/decimal_without_scale.rb b/activemodel/lib/active_model/type/decimal_without_scale.rb deleted file mode 100644 index 985e1038ed..0000000000 --- a/activemodel/lib/active_model/type/decimal_without_scale.rb +++ /dev/null @@ -1,11 +0,0 @@ -require "active_model/type/big_integer" - -module ActiveModel - module Type - class DecimalWithoutScale < BigInteger # :nodoc: - def type - :decimal - end - end - end -end diff --git a/activemodel/lib/active_model/type/helpers/accepts_multiparameter_time.rb b/activemodel/lib/active_model/type/helpers/accepts_multiparameter_time.rb index facea12704..f783d286c5 100644 --- a/activemodel/lib/active_model/type/helpers/accepts_multiparameter_time.rb +++ b/activemodel/lib/active_model/type/helpers/accepts_multiparameter_time.rb @@ -1,7 +1,7 @@ module ActiveModel module Type - module Helpers - class AcceptsMultiparameterTime < Module # :nodoc: + module Helpers # :nodoc: all + class AcceptsMultiparameterTime < Module def initialize(defaults: {}) define_method(:cast) do |value| if value.is_a?(Hash) diff --git a/activemodel/lib/active_model/type/helpers/mutable.rb b/activemodel/lib/active_model/type/helpers/mutable.rb index 4dddbe4e5e..f3a17a1698 100644 --- a/activemodel/lib/active_model/type/helpers/mutable.rb +++ b/activemodel/lib/active_model/type/helpers/mutable.rb @@ -1,7 +1,7 @@ module ActiveModel module Type - module Helpers - module Mutable # :nodoc: + module Helpers # :nodoc: all + module Mutable def cast(value) deserialize(serialize(value)) end diff --git a/activemodel/lib/active_model/type/helpers/numeric.rb b/activemodel/lib/active_model/type/helpers/numeric.rb index 98533f8771..275822b738 100644 --- a/activemodel/lib/active_model/type/helpers/numeric.rb +++ b/activemodel/lib/active_model/type/helpers/numeric.rb @@ -1,7 +1,7 @@ module ActiveModel module Type - module Helpers - module Numeric # :nodoc: + module Helpers # :nodoc: all + module Numeric def cast(value) value = \ case value diff --git a/activemodel/lib/active_model/type/helpers/time_value.rb b/activemodel/lib/active_model/type/helpers/time_value.rb index ad78fd49ec..e57a52104b 100644 --- a/activemodel/lib/active_model/type/helpers/time_value.rb +++ b/activemodel/lib/active_model/type/helpers/time_value.rb @@ -2,8 +2,8 @@ require "active_support/core_ext/time/zones" module ActiveModel module Type - module Helpers - module TimeValue # :nodoc: + module Helpers # :nodoc: all + module TimeValue def serialize(value) value = apply_seconds_precision(value) @@ -34,7 +34,7 @@ module ActiveModel return value unless precision && value.respond_to?(:usec) number_of_insignificant_digits = 6 - precision round_power = 10**number_of_insignificant_digits - value.change(usec: value.usec / round_power * round_power) + value.change(usec: value.usec - value.usec % round_power) end def type_cast_for_schema(value) diff --git a/activemodel/lib/active_model/type/integer.rb b/activemodel/lib/active_model/type/integer.rb index 41dd655a5c..230e45ba60 100644 --- a/activemodel/lib/active_model/type/integer.rb +++ b/activemodel/lib/active_model/type/integer.rb @@ -29,6 +29,8 @@ module ActiveModel result 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 :range diff --git a/activemodel/lib/active_model/type/registry.rb b/activemodel/lib/active_model/type/registry.rb index d25f1a129e..2d5dd366eb 100644 --- a/activemodel/lib/active_model/type/registry.rb +++ b/activemodel/lib/active_model/type/registry.rb @@ -21,6 +21,8 @@ module ActiveModel end 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 :registrations @@ -55,6 +57,8 @@ module ActiveModel type_name == 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 :name, :block diff --git a/activemodel/lib/active_model/type/text.rb b/activemodel/lib/active_model/type/text.rb deleted file mode 100644 index 7c0d647706..0000000000 --- a/activemodel/lib/active_model/type/text.rb +++ /dev/null @@ -1,11 +0,0 @@ -require "active_model/type/string" - -module ActiveModel - module Type - class Text < String # :nodoc: - def type - :text - end - end - end -end diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb index a1f7c971db..d460068830 100644 --- a/activemodel/lib/active_model/validations.rb +++ b/activemodel/lib/active_model/validations.rb @@ -399,14 +399,14 @@ module ActiveModel # end alias :read_attribute_for_validation :send - protected + private - def run_validations! #:nodoc: + def run_validations! _run_validate_callbacks errors.empty? end - def raise_validation_error + def raise_validation_error # :doc: raise(ValidationError.new(self)) end end diff --git a/activemodel/lib/active_model/validations/acceptance.rb b/activemodel/lib/active_model/validations/acceptance.rb index 9826c2fe9c..e11005b9ba 100644 --- a/activemodel/lib/active_model/validations/acceptance.rb +++ b/activemodel/lib/active_model/validations/acceptance.rb @@ -56,6 +56,8 @@ module ActiveModel klass.send(:attr_writer, *attr_writers) 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 :attributes diff --git a/activemodel/lib/active_model/validations/callbacks.rb b/activemodel/lib/active_model/validations/callbacks.rb index a201f72ed0..4e94422cf1 100644 --- a/activemodel/lib/active_model/validations/callbacks.rb +++ b/activemodel/lib/active_model/validations/callbacks.rb @@ -23,7 +23,6 @@ module ActiveModel included do include ActiveSupport::Callbacks define_callbacks :validation, - terminator: deprecated_false_terminator, skip_after_callbacks_if_terminated: true, scope: [:kind, :name] end @@ -104,10 +103,10 @@ module ActiveModel end end - protected + private # Overwrite run validations to include callbacks. - def run_validations! #:nodoc: + def run_validations! _run_validation_callbacks { super } end end diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb index de1524829e..739f8190cc 100644 --- a/activemodel/lib/active_model/validations/length.rb +++ b/activemodel/lib/active_model/validations/length.rb @@ -1,5 +1,3 @@ -require "active_support/core_ext/string/strip" - module ActiveModel module Validations class LengthValidator < EachValidator # :nodoc: diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb index 48e0e5c9f6..2297faaee5 100644 --- a/activemodel/lib/active_model/validations/numericality.rb +++ b/activemodel/lib/active_model/validations/numericality.rb @@ -61,29 +61,29 @@ module ActiveModel end end - protected + private - def is_number?(raw_value) + def is_number?(raw_value) # :doc: !parse_raw_value_as_a_number(raw_value).nil? rescue ArgumentError, TypeError false end - def parse_raw_value_as_a_number(raw_value) + def parse_raw_value_as_a_number(raw_value) # :doc: Kernel.Float(raw_value) if raw_value !~ /\A0[xX]/ end - def is_integer?(raw_value) + def is_integer?(raw_value) # :doc: /\A[+-]?\d+\z/ === raw_value.to_s end - def filtered_options(value) + def filtered_options(value) # :doc: filtered = options.except(*RESERVED_OPTIONS) filtered[:value] = value filtered end - def allow_only_integer?(record) + def allow_only_integer?(record) # :doc: case options[:only_integer] when Symbol record.send(options[:only_integer]) @@ -94,12 +94,10 @@ module ActiveModel end end - private - - def record_attribute_changed_in_place?(record, attr_name) - record.respond_to?(:attribute_changed_in_place?) && - record.attribute_changed_in_place?(attr_name.to_s) - end + def record_attribute_changed_in_place?(record, attr_name) + record.respond_to?(:attribute_changed_in_place?) && + record.attribute_changed_in_place?(attr_name.to_s) + end end module HelperMethods diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb index f95f44de61..0ce5935f3a 100644 --- a/activemodel/lib/active_model/validations/validates.rb +++ b/activemodel/lib/active_model/validations/validates.rb @@ -148,15 +148,15 @@ module ActiveModel validates(*(attributes << options)) end - protected + private # When creating custom validators, it might be useful to be able to specify # additional default keys. This can be done by overwriting this method. - def _validates_default_keys # :nodoc: + def _validates_default_keys [:if, :unless, :on, :allow_blank, :allow_nil , :strict] end - def _parse_validates_options(options) # :nodoc: + def _parse_validates_options(options) case options when TrueClass {} 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/attribute_assignment_test.rb b/activemodel/test/cases/attribute_assignment_test.rb index 8eb389d331..fa41d1b95f 100644 --- a/activemodel/test/cases/attribute_assignment_test.rb +++ b/activemodel/test/cases/attribute_assignment_test.rb @@ -16,6 +16,8 @@ class AttributeAssignmentTest < ActiveModel::TestCase raise ErrorFromAttributeWriter end + # TODO Change this to private once we've dropped Ruby 2.2 support. + # Workaround for Ruby 2.2 "private attribute?" warning. protected attr_writer :metadata diff --git a/activemodel/test/cases/callbacks_test.rb b/activemodel/test/cases/callbacks_test.rb index 63b6c56f8c..f85cd7dec4 100644 --- a/activemodel/test/cases/callbacks_test.rb +++ b/activemodel/test/cases/callbacks_test.rb @@ -27,7 +27,7 @@ class CallbacksTest < ActiveModel::TestCase false end - ActiveSupport::Deprecation.silence { after_create "@callbacks << :final_callback" } + after_create { |model| model.callbacks << :final_callback } def initialize(options = {}) @callbacks = [] @@ -63,12 +63,10 @@ class CallbacksTest < ActiveModel::TestCase assert_equal model.callbacks.last, :final_callback end - test "the callback chain is halted when a before callback returns false (deprecated)" do + test "the callback chain is not halted when a before callback returns false)" do model = ModelCallbacks.new(before_create_returns: false) - assert_deprecated do - model.create - assert_equal model.callbacks.last, :before_create - end + model.create + assert_equal model.callbacks.last, :final_callback end test "the callback chain is halted when a callback throws :abort" do @@ -127,6 +125,7 @@ class CallbacksTest < ActiveModel::TestCase test "after_create callbacks with both callbacks declared in one line" do assert_equal ["callback1", "callback2"], Violin1.new.create.history end + test "after_create callbacks with both callbacks declared in different lines" do assert_equal ["callback1", "callback2"], Violin2.new.create.history end diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb index fc840bf192..0872084cf5 100644 --- a/activemodel/test/cases/errors_test.rb +++ b/activemodel/test/cases/errors_test.rb @@ -1,4 +1,6 @@ require "cases/helper" +require "active_support/core_ext/string/strip" +require "yaml" class ErrorsTest < ActiveModel::TestCase class Person @@ -365,4 +367,24 @@ class ErrorsTest < ActiveModel::TestCase assert_equal errors.messages, serialized.messages assert_equal errors.details, serialized.details end + + test "errors are backward compatible with the Rails 4.2 format" do + yaml = <<-CODE.strip_heredoc + --- !ruby/object:ActiveModel::Errors + base: &1 !ruby/object:ErrorsTest::Person + errors: !ruby/object:ActiveModel::Errors + base: *1 + messages: {} + messages: {} + CODE + + errors = YAML.load(yaml) + errors.add(:name, :invalid) + assert_equal({ name: ["is invalid"] }, errors.messages) + assert_equal({ name: [{ error: :invalid }] }, errors.details) + + errors.clear + assert_equal({}, errors.messages) + assert_equal({}, errors.details) + end end 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/activemodel/test/cases/secure_password_test.rb b/activemodel/test/cases/secure_password_test.rb index 9423df2c09..77ce86f392 100644 --- a/activemodel/test/cases/secure_password_test.rb +++ b/activemodel/test/cases/secure_password_test.rb @@ -179,7 +179,7 @@ class SecurePasswordTest < ActiveModel::TestCase test "setting a nil password should clear an existing password" do @existing_user.password = nil - assert_equal nil, @existing_user.password_digest + assert_nil @existing_user.password_digest end test "authenticate" do diff --git a/activemodel/test/cases/type/binary_test.rb b/activemodel/test/cases/type/binary_test.rb index e6a32dbeec..e9c2ccfca4 100644 --- a/activemodel/test/cases/type/binary_test.rb +++ b/activemodel/test/cases/type/binary_test.rb @@ -6,7 +6,7 @@ module ActiveModel class BinaryTest < ActiveModel::TestCase def test_type_cast_binary type = Type::Binary.new - assert_equal nil, type.cast(nil) + assert_nil type.cast(nil) assert_equal "1", type.cast("1") assert_equal 1, type.cast(1) end diff --git a/activemodel/test/cases/type/date_test.rb b/activemodel/test/cases/type/date_test.rb index 44e20a327b..0cc90e99d3 100644 --- a/activemodel/test/cases/type/date_test.rb +++ b/activemodel/test/cases/type/date_test.rb @@ -6,10 +6,10 @@ module ActiveModel class DateTest < ActiveModel::TestCase def test_type_cast_date type = Type::Date.new - assert_equal nil, type.cast(nil) - assert_equal nil, type.cast("") - assert_equal nil, type.cast(" ") - assert_equal nil, type.cast("ABC") + assert_nil type.cast(nil) + assert_nil type.cast("") + assert_nil type.cast(" ") + assert_nil type.cast("ABC") date_string = ::Time.now.utc.strftime("%F") assert_equal date_string, type.cast(date_string).strftime("%F") diff --git a/activemodel/test/cases/type/date_time_test.rb b/activemodel/test/cases/type/date_time_test.rb index fb82260d2b..75a7fc686e 100644 --- a/activemodel/test/cases/type/date_time_test.rb +++ b/activemodel/test/cases/type/date_time_test.rb @@ -6,10 +6,10 @@ module ActiveModel class DateTimeTest < ActiveModel::TestCase def test_type_cast_datetime_and_timestamp type = Type::DateTime.new - assert_equal nil, type.cast(nil) - assert_equal nil, type.cast("") - assert_equal nil, type.cast(" ") - assert_equal nil, type.cast("ABC") + assert_nil type.cast(nil) + assert_nil type.cast("") + assert_nil type.cast(" ") + assert_nil type.cast("ABC") datetime_string = ::Time.now.utc.strftime("%FT%T") assert_equal datetime_string, type.cast(datetime_string).strftime("%FT%T") diff --git a/activemodel/test/cases/type/time_test.rb b/activemodel/test/cases/type/time_test.rb index a6a79833e6..0cc4d33caa 100644 --- a/activemodel/test/cases/type/time_test.rb +++ b/activemodel/test/cases/type/time_test.rb @@ -6,9 +6,9 @@ module ActiveModel class TimeTest < ActiveModel::TestCase def test_type_cast_time type = Type::Time.new - assert_equal nil, type.cast(nil) - assert_equal nil, type.cast("") - assert_equal nil, type.cast("ABC") + assert_nil type.cast(nil) + assert_nil type.cast("") + assert_nil type.cast("ABC") time_string = ::Time.now.utc.strftime("%T") assert_equal time_string, type.cast(time_string).strftime("%T") diff --git a/activemodel/test/cases/validations/callbacks_test.rb b/activemodel/test/cases/validations/callbacks_test.rb index 83e8ac9522..f2e4a5946d 100644 --- a/activemodel/test/cases/validations/callbacks_test.rb +++ b/activemodel/test/cases/validations/callbacks_test.rb @@ -29,7 +29,7 @@ class DogWithTwoValidators < Dog before_validation { history << "before_validation_marker2" } end -class DogDeprecatedBeforeValidatorReturningFalse < Dog +class DogBeforeValidatorReturningFalse < Dog before_validation { false } before_validation { history << "before_validation_marker2" } end @@ -121,13 +121,11 @@ class CallbacksWithMethodNamesShouldBeCalled < ActiveModel::TestCase assert_equal false, output end - def test_deprecated_further_callbacks_should_not_be_called_if_before_validation_returns_false - d = DogDeprecatedBeforeValidatorReturningFalse.new - assert_deprecated do - output = d.valid? - assert_equal [], d.history - assert_equal false, output - end + def test_further_callbacks_should_be_called_if_before_validation_returns_false + d = DogBeforeValidatorReturningFalse.new + output = d.valid? + assert_equal ["before_validation_marker2"], d.history + assert_equal true, output end def test_further_callbacks_should_be_called_if_after_validation_returns_false diff --git a/activemodel/test/cases/validations/conditional_validation_test.rb b/activemodel/test/cases/validations/conditional_validation_test.rb index 4881008017..048d27446e 100644 --- a/activemodel/test/cases/validations/conditional_validation_test.rb +++ b/activemodel/test/cases/validations/conditional_validation_test.rb @@ -43,7 +43,9 @@ class ConditionalValidationTest < ActiveModel::TestCase def test_if_validation_using_string_true # When the evaluated string returns true - Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", if: "a = 1; a == 1") + ActiveSupport::Deprecation.silence do + Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", if: "a = 1; a == 1") + end t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? @@ -52,7 +54,9 @@ class ConditionalValidationTest < ActiveModel::TestCase def test_unless_validation_using_string_true # When the evaluated string returns true - Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", unless: "a = 1; a == 1") + ActiveSupport::Deprecation.silence do + Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", unless: "a = 1; a == 1") + end t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.valid? assert_empty t.errors[:title] @@ -60,7 +64,9 @@ class ConditionalValidationTest < ActiveModel::TestCase def test_if_validation_using_string_false # When the evaluated string returns false - Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", if: "false") + ActiveSupport::Deprecation.silence do + Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", if: "false") + end t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.valid? assert_empty t.errors[:title] @@ -68,7 +74,9 @@ class ConditionalValidationTest < ActiveModel::TestCase def test_unless_validation_using_string_false # When the evaluated string returns false - Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", unless: "false") + ActiveSupport::Deprecation.silence do + Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", unless: "false") + end t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? @@ -118,7 +126,9 @@ class ConditionalValidationTest < ActiveModel::TestCase # ensure that it works correctly def test_validation_with_if_as_string Topic.validates_presence_of(:title) - Topic.validates_presence_of(:author_name, if: "title.to_s.match('important')") + ActiveSupport::Deprecation.silence do + Topic.validates_presence_of(:author_name, if: "title.to_s.match('important')") + end t = Topic.new assert t.invalid?, "A topic without a title should not be valid" diff --git a/activemodel/test/cases/validations/with_validation_test.rb b/activemodel/test/cases/validations/with_validation_test.rb index 20c11dd852..5ce86738cd 100644 --- a/activemodel/test/cases/validations/with_validation_test.rb +++ b/activemodel/test/cases/validations/with_validation_test.rb @@ -69,26 +69,34 @@ class ValidatesWithTest < ActiveModel::TestCase end test "with if statements that return false" do - Topic.validates_with(ValidatorThatAddsErrors, if: "1 == 2") + ActiveSupport::Deprecation.silence do + Topic.validates_with(ValidatorThatAddsErrors, if: "1 == 2") + end topic = Topic.new assert topic.valid? end test "with if statements that return true" do - Topic.validates_with(ValidatorThatAddsErrors, if: "1 == 1") + ActiveSupport::Deprecation.silence do + Topic.validates_with(ValidatorThatAddsErrors, if: "1 == 1") + end topic = Topic.new assert topic.invalid? assert_includes topic.errors[:base], ERROR_MESSAGE end test "with unless statements that return true" do - Topic.validates_with(ValidatorThatAddsErrors, unless: "1 == 1") + ActiveSupport::Deprecation.silence do + Topic.validates_with(ValidatorThatAddsErrors, unless: "1 == 1") + end topic = Topic.new assert topic.valid? end test "with unless statements that returns false" do - Topic.validates_with(ValidatorThatAddsErrors, unless: "1 == 2") + ActiveSupport::Deprecation.silence do + Topic.validates_with(ValidatorThatAddsErrors, unless: "1 == 2") + end topic = Topic.new assert topic.invalid? assert_includes topic.errors[:base], ERROR_MESSAGE @@ -102,7 +110,9 @@ class ValidatesWithTest < ActiveModel::TestCase validator.expect(:is_a?, false, [Symbol]) validator.expect(:is_a?, false, [String]) - Topic.validates_with(validator, if: "1 == 1", foo: :bar) + ActiveSupport::Deprecation.silence do + Topic.validates_with(validator, if: "1 == 1", foo: :bar) + end assert topic.valid? validator.verify end diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 96d0ad3b30..8a1531e7ec 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,10 +1,299 @@ +* Deprecate passing `default` to `index_name_exists?`. + + *Ryuta Kamizono* + +* PostgreSQL: schema dumping support for interval and OID columns. + + *Ryuta Kamizono* + +* Deprecate `supports_primary_key?` on connection adapters since it's + been long unused and unsupported. + + *Ryuta Kamizono* + +* Make `table_name=` reset current statement cache, + so queries are not run against the previous table name. + + *namusyaka* + +* Allow `ActiveRecord::Base#as_json` to be passed a frozen Hash. + + *Isaac Betesh* + +* Fix inspection behavior when the :id column is not primary key. + + *namusyaka* + +* Deprecate locking records with unpersisted changes. + + *Marc Schütz* + +* Remove deprecated behavior that halts callbacks when the return is false. + + *Rafael Mendonça França* + +* 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. + + *Richard Monette* + +* RuntimeErrors are no longer translated to `ActiveRecord::StatementInvalid`. + + *Richard Monette* + +* Change the schema cache format to use YAML instead of Marshal. + + *Kir Shatrov* + +* Support index length and order options using both string and symbol + column names. + + Fixes #27243. + + *Ryuta Kamizono* + +* Raise `ActiveRecord::RangeError` when values that executed are out of range. + + *Ryuta Kamizono* + +* Raise `ActiveRecord::NotNullViolation` when a record cannot be inserted + or updated because it would violate a not null constraint. + + *Ryuta Kamizono* + +* Emulate db trigger behaviour for after_commit :destroy, :update. + + Race conditions can occur when an ActiveRecord is destroyed + twice or destroyed and updated. The callbacks should only be + triggered once, similar to a SQL database trigger. + + *Stefan Budeanu* + +* Moved `DecimalWithoutScale`, `Text`, and `UnsignedInteger` from Active Model to Active Record. + + *Iain Beeston* + +* Fix `write_attribute` method to check whether an attribute is aliased or not, and + use the aliased attribute name if needed. + + *Prathamesh Sonpatki* + +* Fix `read_attribute` method to check whether an attribute is aliased or not, and + use the aliased attribute name if needed. + + Fixes #26417. + + *Prathamesh Sonpatki* + +* PostgreSQL & MySQL: Use big integer as primary key type for new tables. + + *Jon McCartie*, *Pavel Pravosud* + * Change the type argument of `ActiveRecord::Base#attribute` to be optional. The default is now `ActiveRecord::Type::Value.new`, which provides no type casting behavior. *Sean Griffin* -* Fix that unsigned with zerofill is treated as signed. +* Don't treat unsigned integers with zerofill as signed. Fixes #27125. @@ -14,7 +303,7 @@ *Sergey Alekseev* -* Raise ActiveRecord::RecordNotFound from collection `*_ids` setters +* Raise `ActiveRecord::RecordNotFound` from collection `*_ids` setters for unknown IDs with a better error message. Changes the collection `*_ids` setters to cast provided IDs the data @@ -54,7 +343,7 @@ *Jon Moss* -* Added `stat` method to `ActiveRecord::ConnectionAdapters::ConnectionPool` +* Added `stat` method to `ActiveRecord::ConnectionAdapters::ConnectionPool`. Example: @@ -101,7 +390,7 @@ * Fixed: Optimistic locking does not work well with `null` in the database. - Fixes #26024 + Fixes #26024. *bogdanvlviv* @@ -110,7 +399,7 @@ *Edho Arief* -* Serialize JSON attribute value `nil` as SQL `NULL`, not JSON `null` +* Serialize JSON attribute value `nil` as SQL `NULL`, not JSON `null`. *Trung Duc Tran* @@ -210,11 +499,11 @@ *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. - Fixes #26087 + Fixes #26087. *Travis O'Neill* @@ -244,7 +533,7 @@ *Takeshi Akima* * Virtual attributes will no longer raise when read on models loaded from the - database + database. *Sean Griffin* 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/aggregations.rb b/activerecord/lib/active_record/aggregations.rb index 08dfc3a64f..10cbd5429c 100644 --- a/activerecord/lib/active_record/aggregations.rb +++ b/activerecord/lib/active_record/aggregations.rb @@ -15,11 +15,11 @@ module ActiveRecord private - def clear_aggregation_cache # :nodoc: + def clear_aggregation_cache @aggregation_cache.clear if persisted? end - def init_internals # :nodoc: + def init_internals @aggregation_cache = {} super end diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 19308643f3..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 @@ -260,11 +285,11 @@ module ActiveRecord private # Clears out the association cache. - def clear_association_cache # :nodoc: + def clear_association_cache @association_cache.clear if persisted? end - def init_internals # :nodoc: + def init_internals @association_cache = {} super 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 12f8c1ccd4..badde9973f 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -49,6 +49,8 @@ module ActiveRecord binds 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 :value_transformation @@ -126,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 @@ -136,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) @@ -151,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 3d23fa1e46..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}" @@ -75,7 +65,7 @@ module ActiveRecord r.send(reflection.association_primary_key) end.values_at(*ids).compact if records.size != ids.size - scope.raise_record_not_found_exception!(ids, records.size, ids.size, reflection.association_primary_key) + klass.all.raise_record_not_found_exception!(ids, records.size, ids.size, reflection.association_primary_key) else replace(records) end @@ -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 35a98d7090..0d84805b4d 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -1126,7 +1126,7 @@ module ActiveRecord self end - protected + private def find_nth_with_limit(index, limit) load_target if find_from_target? @@ -1138,8 +1138,6 @@ module ActiveRecord super end - private - def null_scope? @association.null_scope? 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 1953cc6a72..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 @@ -58,6 +50,8 @@ module ActiveRecord sc.execute(binds, klass, conn) do |record| set_inverse_instance record end.first + rescue ::RangeError + nil end def replace(record) diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb index f4129edc5a..6b87993ba3 100644 --- a/activerecord/lib/active_record/associations/through_association.rb +++ b/activerecord/lib/active_record/associations/through_association.rb @@ -4,7 +4,7 @@ module ActiveRecord module ThroughAssociation #:nodoc: delegate :source_reflection, :through_reflection, to: :reflection - protected + private # We merge in these scopes for two reasons: # @@ -21,8 +21,6 @@ module ActiveRecord scope end - private - # Construct attributes for :through pointing to owner and associate. This is used by the # methods which create and delete records on the association. # diff --git a/activerecord/lib/active_record/attribute.rb b/activerecord/lib/active_record/attribute.rb index 0b08c2a39b..38281158d8 100644 --- a/activerecord/lib/active_record/attribute.rb +++ b/activerecord/lib/active_record/attribute.rb @@ -128,11 +128,22 @@ module ActiveRecord coder["value"] = value if defined?(@value) 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 :original_attribute alias_method :assigned?, :original_attribute + def original_value_for_database + if assigned? + original_attribute.original_value_for_database + else + _original_value_for_database + end + end + + private def initialize_dup(other) if defined?(@value) && @value.duplicable? @value = @value.dup @@ -143,14 +154,6 @@ module ActiveRecord assigned? && type.changed?(original_value, value, value_before_type_cast) end - def original_value_for_database - if assigned? - original_attribute.original_value_for_database - else - _original_value_for_database - end - end - def _original_value_for_database type.serialize(original_value) end diff --git a/activerecord/lib/active_record/attribute/user_provided_default.rb b/activerecord/lib/active_record/attribute/user_provided_default.rb index a4e2c2ec85..57f8bbed76 100644 --- a/activerecord/lib/active_record/attribute/user_provided_default.rb +++ b/activerecord/lib/active_record/attribute/user_provided_default.rb @@ -20,6 +20,8 @@ module ActiveRecord self.class.new(name, user_provided_value, type, original_attribute) 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 :user_provided_value diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb index 9843e0ca66..d0dfca0cac 100644 --- a/activerecord/lib/active_record/attribute_assignment.rb +++ b/activerecord/lib/active_record/attribute_assignment.rb @@ -12,7 +12,7 @@ module ActiveRecord private - def _assign_attributes(attributes) # :nodoc: + def _assign_attributes(attributes) multi_parameter_attributes = {} nested_parameter_attributes = {} diff --git a/activerecord/lib/active_record/attribute_decorators.rb b/activerecord/lib/active_record/attribute_decorators.rb index 340dfe11cf..c39e9ce4c5 100644 --- a/activerecord/lib/active_record/attribute_decorators.rb +++ b/activerecord/lib/active_record/attribute_decorators.rb @@ -8,12 +8,34 @@ module ActiveRecord end module ClassMethods # :nodoc: + # This method is an internal API used to create class macros such as + # +serialize+, and features like time zone aware attributes. + # + # Used to wrap the type of an attribute in a new type. + # When the schema for a model is loaded, attributes with the same name as + # +column_name+ will have their type yielded to the given block. The + # return value of that block will be used instead. + # + # Subsequent calls where +column_name+ and +decorator_name+ are the same + # will override the previous decorator, not decorate twice. This can be + # used to create idempotent class macros like +serialize+ def decorate_attribute_type(column_name, decorator_name, &block) matcher = ->(name, _) { name == column_name.to_s } key = "_#{column_name}_#{decorator_name}" decorate_matching_attribute_types(matcher, key, &block) end + # This method is an internal API used to create higher level features like + # time zone aware attributes. + # + # When the schema for a model is loaded, +matcher+ will be called for each + # attribute with its name and type. If the matcher returns a truthy value, + # the type will then be yielded to the given block, and the return value + # of that block will replace the type. + # + # Subsequent calls to this method with the same value for +decorator_name+ + # will replace the previous decorator, not decorate twice. This can be + # used to ensure that class macros are idempotent. def decorate_matching_attribute_types(matcher, decorator_name, &block) reload_schema_from_cache decorator_name = decorator_name.to_s 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..31c1e687dc 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 @@ -270,7 +275,17 @@ module ActiveRecord def attribute_will_change!(attr_name) super - mutations_from_database.force_change(attr_name) + if self.class.has_attribute?(attr_name) + mutations_from_database.force_change(attr_name) + else + ActiveSupport::Deprecation.warn(<<-EOW.squish) + #{attr_name} is not an attribute known to Active Record. + This behavior is deprecated and will be removed in the next + version of Rails. If you'd like #{attr_name} to be managed + by Active Record, add `attribute :#{attr_name} to your class. + EOW + mutations_from_database.deprecated_force_change(attr_name) + end end def _update_record(*) diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb index 287367f92a..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 @@ -50,7 +50,7 @@ module ActiveRecord attribute_in_database(self.class.primary_key) end - protected + private def attribute_method?(attr_name) attr_name == "id" || super diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 30f7750884..fdc4bf6621 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -4,7 +4,7 @@ module ActiveRecord extend ActiveSupport::Concern module ClassMethods - protected + private # We want to generate the methods via module_eval rather than # define_method, because define_method is slower on dispatch. @@ -48,8 +48,13 @@ module ActiveRecord # it has been typecast (for example, "2004-12-12" in a date column is cast # to a date object, like Date.new(2004, 12, 12)). def read_attribute(attr_name, &block) - name = attr_name.to_s - name = self.class.primary_key if name == "id".freeze + name = if self.class.attribute_alias?(attr_name) + self.class.attribute_alias(attr_name).to_s + else + attr_name.to_s + end + + name = self.class.primary_key if name == "id".freeze && self.class.primary_key _read_attribute(name, &block) end 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..321d039ed4 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -1,5 +1,3 @@ -require "active_support/core_ext/string/strip" - module ActiveRecord module AttributeMethods module TimeZoneConversion @@ -63,7 +61,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 +84,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 f65c297e01..fe0e01db28 100644 --- a/activerecord/lib/active_record/attribute_methods/write.rb +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -8,7 +8,7 @@ module ActiveRecord end module ClassMethods - protected + private def define_method_attribute=(name) safe_name = name.unpack("h*".freeze).first @@ -29,7 +29,13 @@ module ActiveRecord # specified +value+. Empty strings for Integer and Float columns are # turned into +nil+. def write_attribute(attr_name, value) - write_attribute_with_type_cast(attr_name, value, true) + name = if self.class.attribute_alias?(attr_name) + self.class.attribute_alias(attr_name).to_s + else + attr_name.to_s + end + + write_attribute_with_type_cast(name, value, true) end def raw_write_attribute(attr_name, value) # :nodoc: diff --git a/activerecord/lib/active_record/attribute_mutation_tracker.rb b/activerecord/lib/active_record/attribute_mutation_tracker.rb index db86b2b294..4de993e169 100644 --- a/activerecord/lib/active_record/attribute_mutation_tracker.rb +++ b/activerecord/lib/active_record/attribute_mutation_tracker.rb @@ -5,6 +5,7 @@ module ActiveRecord def initialize(attributes) @attributes = attributes @forced_changes = Set.new + @deprecated_forced_changes = Set.new end def changed_values @@ -31,7 +32,7 @@ module ActiveRecord end def any_changes? - attr_names.any? { |attr| changed?(attr) } + attr_names.any? { |attr| changed?(attr) } || deprecated_forced_changes.any? end def changed?(attr_name, from: OPTION_NOT_GIVEN, to: OPTION_NOT_GIVEN) @@ -60,9 +61,15 @@ module ActiveRecord forced_changes << attr_name.to_s end + def deprecated_force_change(attr_name) + deprecated_forced_changes << attr_name.to_s + 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 :attributes, :forced_changes + attr_reader :attributes, :forced_changes, :deprecated_forced_changes private diff --git a/activerecord/lib/active_record/attribute_set.rb b/activerecord/lib/active_record/attribute_set.rb index 5bde1f107c..66b278219a 100644 --- a/activerecord/lib/active_record/attribute_set.rb +++ b/activerecord/lib/active_record/attribute_set.rb @@ -98,6 +98,8 @@ module ActiveRecord attributes == other.attributes 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 :attributes diff --git a/activerecord/lib/active_record/attribute_set/builder.rb b/activerecord/lib/active_record/attribute_set/builder.rb index 661f996e1a..2f624d32af 100644 --- a/activerecord/lib/active_record/attribute_set/builder.rb +++ b/activerecord/lib/active_record/attribute_set/builder.rb @@ -90,6 +90,8 @@ module ActiveRecord @materialized = true 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 :types, :values, :additional_types, :delegate_hash, :default diff --git a/activerecord/lib/active_record/attribute_set/yaml_encoder.rb b/activerecord/lib/active_record/attribute_set/yaml_encoder.rb index c86cfc4263..899de14792 100644 --- a/activerecord/lib/active_record/attribute_set/yaml_encoder.rb +++ b/activerecord/lib/active_record/attribute_set/yaml_encoder.rb @@ -31,6 +31,8 @@ module ActiveRecord end 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 :default_types diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index b343332bae..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) @@ -383,6 +383,9 @@ module ActiveRecord if association = association_instance_get(reflection.name) autosave = reflection.options[:autosave] + # reconstruct the scope now that we know the owner's id + association.reset_scope if association.respond_to?(:reset_scope) + if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave) if autosave records_to_destroy = records.select(&:marked_for_destruction?) @@ -408,9 +411,6 @@ module ActiveRecord raise ActiveRecord::Rollback unless saved end end - - # reconstruct the scope now that we know the owner's id - association.reset_scope if association.respond_to?(:reset_scope) end end diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index f2e3912c6e..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!], @@ -283,15 +332,15 @@ module ActiveRecord private - def create_or_update(*) #:nodoc: + def create_or_update(*) _run_save_callbacks { super } end - def _create_record #:nodoc: + def _create_record _run_create_callbacks { super } end - def _update_record(*) #:nodoc: + def _update_record(*) _run_update_callbacks { super } end end 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 faccd1d641..769f488469 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -10,9 +10,9 @@ module ActiveRecord def to_sql(arel, binds = []) if arel.respond_to?(:ast) collected = visitor.accept(arel.ast, collector) - collected.compile(binds, self) + collected.compile(binds, self).freeze else - arel + arel.dup.freeze end end @@ -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 @@ -360,7 +347,7 @@ module ActiveRecord end alias join_to_delete join_to_update - protected + private # Returns a subquery for the given key using the join information. def subquery_for(key, select) diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index bbd52b8a91..7f4132accf 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 @@ -150,6 +141,10 @@ module ActiveRecord quoted_date(value).sub(/\A2000-01-01 /, "") end + def quoted_binary(value) # :nodoc: + "'#{quote_string(value.to_s)}'" + end + private def type_casted_binds(binds) @@ -162,7 +157,7 @@ module ActiveRecord def _quote(value) case value - when String, ActiveSupport::Multibyte::Chars, Type::Binary::Data + when String, ActiveSupport::Multibyte::Chars "'#{quote_string(value.to_s)}'" when true then quoted_true when false then quoted_false @@ -170,6 +165,7 @@ module ActiveRecord # BigDecimals need to be put in a non-normalized form and quoted. when BigDecimal then value.to_s("F") when Numeric, ActiveSupport::Duration then value.to_s + when Type::Binary::Data then quoted_binary(value) when Type::Time::Value then "'#{quoted_time(value)}'" when Date, Time then "'#{quoted_date(value)}'" when Symbol then "'#{quote_string(value.to_s)}'" 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..c48a4acff8 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 @@ -29,7 +29,7 @@ module ActiveRecord end def visit_ColumnDefinition(o) - o.sql_type ||= type_to_sql(o.type, o.limit, o.precision, o.scale) + o.sql_type = type_to_sql(o.type, o.options) column_sql = "#{quote_column_name(o.name)} #{o.sql_type}" add_column_options!(column_sql, column_options(o)) unless o.type == :primary_key column_sql @@ -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 @@ -96,17 +96,7 @@ module ActiveRecord end def column_options(o) - column_options = {} - column_options[:null] = o.null unless o.null.nil? - column_options[:default] = o.default unless o.default.nil? - column_options[:column] = o - column_options[:first] = o.first - column_options[:after] = o.after - column_options[:auto_increment] = o.auto_increment - column_options[:primary_key] = o.primary_key - column_options[:collation] = o.collation - column_options[:comment] = o.comment - column_options + o.options.merge(column: o) end def add_column_options!(sql, options) 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 83d1d7cd01..5eb7787226 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,37 @@ 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, :options, :sql_type) do # :nodoc: def primary_key? - primary_key || type.to_sym == :primary_key + options[:primary_key] end - end - class AddColumnDefinition < Struct.new(:column) # :nodoc: - end + [:limit, :precision, :scale, :default, :null, :collation, :comment].each do |option_name| + module_eval <<-CODE, __FILE__, __LINE__ + 1 + def #{option_name} + options[:#{option_name}] + end - class ChangeColumnDefinition < Struct.new(:column, :name) #:nodoc: + def #{option_name}=(value) + options[:#{option_name}] = value + end + CODE + end end - class PrimaryKeyDefinition < Struct.new(:name) # :nodoc: - end + AddColumnDefinition = Struct.new(:column) # :nodoc: + + ChangeColumnDefinition = Struct.new(:column, :name) #:nodoc: - class ForeignKeyDefinition < Struct.new(:from_table, :to_table, :options) #:nodoc: + PrimaryKeyDefinition = Struct.new(:name) # :nodoc: + + ForeignKeyDefinition = Struct.new(:from_table, :to_table, :options) do #:nodoc: def name options[:name] end @@ -71,7 +79,7 @@ module ActiveRecord polymorphic: false, index: true, foreign_key: false, - type: :integer, + type: :bigint, **options ) @name = name @@ -100,6 +108,8 @@ module ActiveRecord end 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 :name, :polymorphic, :index, :foreign_key, :type, :options @@ -175,6 +185,7 @@ module ActiveRecord :text, :time, :timestamp, + :virtual, ].each do |column_type| module_eval <<-CODE, __FILE__, __LINE__ + 1 def #{column_type}(*args, **options) @@ -355,33 +366,22 @@ module ActiveRecord # # See {connection.add_reference}[rdoc-ref:SchemaStatements#add_reference] for details of the options you can use. def references(*args, **options) - args.each do |col| - ReferenceDefinition.new(col, **options).add_to(self) + args.each do |ref_name| + ReferenceDefinition.new(ref_name, options).add_to(self) end end alias :belongs_to :references - def new_column_definition(name, type, options) # :nodoc: + def new_column_definition(name, type, **options) # :nodoc: type = aliased_types(type.to_s, type) - column = create_column_definition name, type - - column.limit = options[:limit] - column.precision = options[:precision] - column.scale = options[:scale] - column.default = options[:default] - column.null = options[:null] - column.first = options[:first] - column.after = options[:after] - column.auto_increment = options[:auto_increment] - column.primary_key = type == :primary_key || options[:primary_key] - column.collation = options[:collation] - column.comment = options[:comment] - column + options[:primary_key] ||= type == :primary_key + options[:null] = false if options[:primary_key] + create_column_definition(name, type, options) end private - def create_column_definition(name, type) - ColumnDefinition.new name, type + def create_column_definition(name, type, options) + ColumnDefinition.new(name, type, options) end def aliased_types(name, fallback) @@ -589,8 +589,7 @@ module ActiveRecord # t.belongs_to(:supplier, foreign_key: true) # # See {connection.add_reference}[rdoc-ref:SchemaStatements#add_reference] for details of the options you can use. - def references(*args) - options = args.extract_options! + def references(*args, **options) args.each do |ref_name| @base.add_reference(name, ref_name, options) end @@ -603,8 +602,7 @@ module ActiveRecord # t.remove_belongs_to(:supplier, polymorphic: true) # # See {connection.remove_reference}[rdoc-ref:SchemaStatements#remove_reference] - def remove_references(*args) - options = args.extract_options! + def remove_references(*args, **options) args.each do |ref_name| @base.remove_reference(name, ref_name, options) 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 dabccc00bb..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,14 +51,27 @@ 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 def default_primary_key?(column) - schema_type(column) == :integer + 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) 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 151629b02a..c61aae85d3 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 @@ -771,11 +773,12 @@ module ActiveRecord end # Verifies the existence of an index with a given name. - # - # The default argument is returned if the underlying implementation does not define the indexes method, - # as there's no way to determine the correct answer in that case. - def index_name_exists?(table_name, index_name, default) - return default unless respond_to?(:indexes) + def index_name_exists?(table_name, index_name, default = nil) + unless default.nil? + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Passing default to #index_name_exists? is deprecated without replacement. + MSG + end index_name = index_name.to_s indexes(table_name).detect { |i| i.name == index_name } end @@ -826,8 +829,8 @@ module ActiveRecord # # add_reference(:products, :supplier, foreign_key: {to_table: :firms}) # - def add_reference(table_name, *args) - ReferenceDefinition.new(*args).add_to(update_table_definition(table_name, self)) + def add_reference(table_name, ref_name, **options) + ReferenceDefinition.new(ref_name, options).add_to(update_table_definition(table_name, self)) end alias :add_belongs_to :add_reference @@ -994,29 +997,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 supports_multi_insert? + 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 - versions.map { |version| - "INSERT INTO #{sm_table} (version) VALUES ('#{version}');" - }.join "\n\n" + "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 } @@ -1034,7 +1035,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 } @@ -1042,11 +1043,17 @@ module ActiveRecord if (duplicate = inserting.detect { |v| inserting.count(v) > 1 }) raise "Duplicate migration #{duplicate}. Please renumber your migrations to resolve the conflict." end - execute insert_versions_sql(inserting) + if supports_multi_insert? + execute insert_versions_sql(inserting) + else + inserting.each do |v| + execute insert_versions_sql(v) + end + end end end - def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc: + def type_to_sql(type, limit: nil, precision: nil, scale: nil, **) # :nodoc: type = type.to_sym if type if native = native_database_types[type] column_type_sql = (native.is_a?(Hash) ? native[:name] : native).dup @@ -1064,7 +1071,7 @@ module ActiveRecord raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale is specified" end - elsif [:datetime, :time].include?(type) && precision ||= native[:precision] + elsif [:datetime, :time, :interval].include?(type) && precision ||= native[:precision] if (0..6) === precision column_type_sql << "(#{precision})" else @@ -1143,7 +1150,7 @@ module ActiveRecord validate_index_length!(table_name, index_name, options.fetch(:internal, false)) - if data_source_exists?(table_name) && index_name_exists?(table_name, index_name, false) + if data_source_exists?(table_name) && index_name_exists?(table_name, index_name) raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists" end index_columns = quoted_columns_for_index(column_names, options).join(", ") @@ -1165,12 +1172,16 @@ module ActiveRecord raise NotImplementedError, "#{self.class} does not support changing column comments" end - protected + private + def column_options_keys + [:limit, :precision, :scale, :default, :null, :collation, :comment] + end def add_index_sort_order(quoted_columns, **options) if order = options[:order] case order when Hash + order = order.symbolize_keys quoted_columns.each { |name, column| column << " #{order[name].upcase}" if order[name].present? } when String quoted_columns.each { |name, column| column << " #{order.upcase}" if order.present? } @@ -1248,7 +1259,6 @@ module ActiveRecord end end - private def create_table_definition(*args) TableDefinition.new(*args) end @@ -1257,7 +1267,7 @@ module ActiveRecord AlterTable.new create_table_definition(name) end - def index_name_options(column_names) # :nodoc: + def index_name_options(column_names) if column_names.is_a?(String) column_names = column_names.scan(/\w+/).join("_") end @@ -1265,7 +1275,7 @@ module ActiveRecord { column: column_names } end - def foreign_key_name(table_name, options) # :nodoc: + def foreign_key_name(table_name, options) identifier = "#{table_name}_#{options.fetch(:column)}_fk" hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10) options.fetch(:name) do @@ -1273,7 +1283,7 @@ module ActiveRecord end end - def validate_index_length!(table_name, new_name, internal = false) # :nodoc: + def validate_index_length!(table_name, new_name, internal = false) max_index_length = internal ? index_name_length : allowed_index_name_length if new_name.length > max_index_length diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 237367c8b3..808de5daca 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 @@ -236,11 +236,10 @@ module ActiveRecord false end - # Can this adapter determine the primary key for tables not attached - # to an Active Record class, such as join tables? - def supports_primary_key? - false + def supports_primary_key? # :nodoc: + true end + deprecate :supports_primary_key? # Does this adapter support DDL rollbacks in transactions? That is, would # CREATE TABLE or ALTER TABLE get rolled back by a transaction? @@ -310,6 +309,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 +345,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 @@ -441,15 +451,15 @@ module ActiveRecord @connection end - def case_sensitive_comparison(table, attribute, column, value) - table[attribute].eq(Arel::Nodes::BindParam.new) + def case_sensitive_comparison(table, attribute, column, value) # :nodoc: + table[attribute].eq(value) end - def case_insensitive_comparison(table, attribute, column, value) + def case_insensitive_comparison(table, attribute, column, value) # :nodoc: if can_perform_case_insensitive_comparison_for?(column) - table[attribute].lower.eq(table.lower(Arel::Nodes::BindParam.new)) + table[attribute].lower.eq(table.lower(value)) else - table[attribute].eq(Arel::Nodes::BindParam.new) + table[attribute].eq(value) end end @@ -499,9 +509,9 @@ module ActiveRecord result end - protected + private - def initialize_type_map(m) # :nodoc: + def initialize_type_map(m) register_class_with_limit m, %r(boolean)i, Type::Boolean register_class_with_limit m, %r(char)i, Type::String register_class_with_limit m, %r(binary)i, Type::Binary @@ -532,37 +542,37 @@ module ActiveRecord end end - def reload_type_map # :nodoc: + def reload_type_map type_map.clear initialize_type_map(type_map) end - def register_class_with_limit(mapping, key, klass) # :nodoc: + def register_class_with_limit(mapping, key, klass) mapping.register_type(key) do |*args| limit = extract_limit(args.last) klass.new(limit: limit) end end - def register_class_with_precision(mapping, key, klass) # :nodoc: + def register_class_with_precision(mapping, key, klass) mapping.register_type(key) do |*args| precision = extract_precision(args.last) klass.new(precision: precision) end end - def extract_scale(sql_type) # :nodoc: + def extract_scale(sql_type) case sql_type when /\((\d+)\)/ then 0 when /\((\d+)(,(\d+))\)/ then $3.to_i end end - def extract_precision(sql_type) # :nodoc: + def extract_precision(sql_type) $1.to_i if sql_type =~ /\((\d+)(,\d+)?\)/ end - def extract_limit(sql_type) # :nodoc: + def extract_limit(sql_type) case sql_type when /^bigint/i 8 @@ -583,7 +593,7 @@ module ActiveRecord exception end - def log(sql, name = "SQL", binds = [], type_casted_binds = [], statement_name = nil) + def log(sql, name = "SQL", binds = [], type_casted_binds = [], statement_name = nil) # :doc: @instrumenter.instrument( "sql.active_record", sql: sql, @@ -598,14 +608,19 @@ module ActiveRecord def translate_exception(exception, message) # override in derived class - ActiveRecord::StatementInvalid.new(message) + case exception + when RuntimeError + exception + else + ActiveRecord::StatementInvalid.new(message) + end end def without_prepared_statement?(binds) !prepared_statements || binds.empty? end - def column_for(table_name, column_name) # :nodoc: + def column_for(table_name, column_name) column_name = column_name.to_s columns(table_name).detect { |c| c.name == column_name } || raise(ActiveRecordError, "No such column: #{table_name}.#{column_name}") 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 98152853c2..f743c80372 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -39,7 +39,7 @@ module ActiveRecord self.emulate_booleans = true NATIVE_DATABASE_TYPES = { - primary_key: "int auto_increment PRIMARY KEY", + primary_key: "bigint auto_increment PRIMARY KEY", string: { name: "varchar", limit: 255 }, text: { name: "text", limit: 65535 }, integer: { name: "int", limit: 4 }, @@ -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 @@ -93,10 +93,6 @@ module ActiveRecord true end - def supports_primary_key? - true - end - def supports_bulk_alter? #:nodoc: true end @@ -141,6 +137,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 +314,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? + + schema, name = extract_schema_qualified_name(table_name) - 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 + 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 +354,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 +365,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 +531,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| @@ -568,7 +570,7 @@ module ActiveRecord end # Maps logical Rails types to MySQL-specific data types. - def type_to_sql(type, limit = nil, precision = nil, scale = nil, unsigned = nil) + def type_to_sql(type, limit: nil, precision: nil, scale: nil, unsigned: nil, **) # :nodoc: sql = \ case type.to_s when "integer" @@ -584,7 +586,7 @@ module ActiveRecord binary_to_sql(limit) end else - super(type, limit, precision, scale) + super end sql << " unsigned" if unsigned && type != :primary_key @@ -613,9 +615,9 @@ module ActiveRecord SQL end - def case_sensitive_comparison(table, attribute, column, value) + def case_sensitive_comparison(table, attribute, column, value) # :nodoc: if column.collation && !column.case_sensitive? - table[attribute].eq(Arel::Nodes::Bin.new(Arel::Nodes::BindParam.new)) + table[attribute].eq(Arel::Nodes::Bin.new(value)) else super end @@ -649,9 +651,9 @@ module ActiveRecord !native_database_types[type].nil? end - protected + private - def initialize_type_map(m) # :nodoc: + def initialize_type_map(m) super register_class_with_limit m, %r(char)i, MysqlString @@ -691,7 +693,7 @@ module ActiveRecord end end - def register_integer_type(mapping, key, options) # :nodoc: + def register_integer_type(mapping, key, options) mapping.register_type(key) do |sql_type| if /\bunsigned\b/.match?(sql_type) Type::UnsignedInteger.new(options) @@ -717,6 +719,7 @@ module ActiveRecord if length = options[:length] case length when Hash + length = length.symbolize_keys quoted_columns.each { |name, column| column << "(#{length[name]})" if length[name].present? } when Integer quoted_columns.each { |name, column| column << "(#{length})" } @@ -733,9 +736,14 @@ module ActiveRecord # See https://dev.mysql.com/doc/refman/5.7/en/error-messages-server.html ER_DUP_ENTRY = 1062 + ER_NOT_NULL_VIOLATION = 1048 + ER_DO_NOT_HAVE_DEFAULT = 1364 ER_NO_REFERENCED_ROW_2 = 1452 ER_DATA_TOO_LONG = 1406 + ER_OUT_OF_RANGE = 1264 ER_LOCK_DEADLOCK = 1213 + ER_CANNOT_ADD_FOREIGN = 1215 + ER_CANNOT_CREATE_TABLE = 1005 def translate_exception(exception, message) case error_number(exception) @@ -743,8 +751,20 @@ module ActiveRecord RecordNotUnique.new(message) when ER_NO_REFERENCED_ROW_2 InvalidForeignKey.new(message) + when ER_CANNOT_ADD_FOREIGN + mismatched_foreign_key(message) + when ER_CANNOT_CREATE_TABLE + if message.include?("errno: 150") + mismatched_foreign_key(message) + else + super + end when ER_DATA_TOO_LONG ValueTooLong.new(message) + when ER_OUT_OF_RANGE + RangeError.new(message) + when ER_NOT_NULL_VIOLATION, ER_DO_NOT_HAVE_DEFAULT + NotNullViolation.new(message) when ER_LOCK_DEADLOCK Deadlocked.new(message) else @@ -769,6 +789,10 @@ module ActiveRecord options[:null] = column.null end + unless options.key?(:comment) + options[:comment] = column.comment + end + td = create_table_definition(table_name) cd = td.new_column_definition(column.name, type, options) schema_creation.accept(ChangeColumnDefinition.new(cd, column.name)) @@ -815,8 +839,6 @@ module ActiveRecord [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)] end - private - # MySQL is too stupid to create a temporary table for use subquery, so we have # to give it some prompting in the form of a subsubquery. Ugh! def subquery_for(key, select) @@ -886,7 +908,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: @@ -910,6 +932,18 @@ module ActiveRecord MySQL::TableDefinition.new(*args) end + def mismatched_foreign_key(message) + parts = message.scan(/`(\w+)`[ $)]/).flatten + MismatchedForeignKey.new( + self, + message: message, + table: parts[0], + foreign_key: parts[1], + target_table: parts[2], + primary_key: parts[3], + ) + end + def extract_schema_qualified_name(string) # :nodoc: schema, name = string.to_s.scan(/[^`.\s]+|`[^`]*`/) schema, name = @config[:database], schema unless name diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb index 02d546209d..61cd7ae4cc 100644 --- a/activerecord/lib/active_record/connection_adapters/column.rb +++ b/activerecord/lib/active_record/connection_adapters/column.rb @@ -40,6 +40,28 @@ module ActiveRecord Base.human_attribute_name(@name) end + def init_with(coder) + @name = coder["name"] + @table_name = coder["table_name"] + @sql_type_metadata = coder["sql_type_metadata"] + @null = coder["null"] + @default = coder["default"] + @default_function = coder["default_function"] + @collation = coder["collation"] + @comment = coder["comment"] + end + + def encode_with(coder) + coder["name"] = @name + coder["table_name"] = @table_name + coder["sql_type_metadata"] = @sql_type_metadata + coder["null"] = @null + coder["default"] = @default + coder["default_function"] = @default_function + coder["collation"] = @collation + coder["comment"] = @comment + end + def ==(other) other.is_a?(Column) && attributes_for_hash == other.attributes_for_hash diff --git a/activerecord/lib/active_record/connection_adapters/mysql/column.rb b/activerecord/lib/active_record/connection_adapters/mysql/column.rb index 22b9df5309..c9ad47c035 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/column.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/column.rb @@ -5,7 +5,7 @@ module ActiveRecord delegate :extra, to: :sql_type_metadata, allow_nil: true def unsigned? - !/\A(?:enum|set)\b/.match?(sql_type) && /\bunsigned\b/.match?(sql_type) + /\bunsigned(?: zerofill)?\z/.match?(sql_type) end def case_sensitive? @@ -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 c7098105a8..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 @@ -52,15 +52,15 @@ module ActiveRecord end alias :exec_update :exec_delete - protected + private def last_inserted_id(result) @connection.last_id end - private - - 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/quoting.rb b/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb index 9d11ad28d4..d4f5906b33 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb @@ -36,15 +36,9 @@ module ActiveRecord end end - private - - def _quote(value) - if value.is_a?(Type::Binary::Data) - "x'#{value.hex}'" - else - super - end - end + def quoted_binary(value) + "x'#{value.hex}'" + end end end end 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..e8358271ab 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb @@ -1,9 +1,9 @@ module ActiveRecord module ConnectionAdapters module MySQL - class SchemaCreation < AbstractAdapter::SchemaCreation - delegate :add_sql_comment!, to: :@conn - private :add_sql_comment! + class SchemaCreation < AbstractAdapter::SchemaCreation # :nodoc: + delegate :add_sql_comment!, :mariadb?, to: :@conn + private :add_sql_comment!, :mariadb? private @@ -11,11 +11,6 @@ module ActiveRecord "DROP FOREIGN KEY #{name}" end - def visit_ColumnDefinition(o) - o.sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale, o.unsigned) - super - end - def visit_AddColumnDefinition(o) add_column_position!(super, column_options(o.column)) end @@ -29,12 +24,6 @@ module ActiveRecord add_sql_comment!(super, options[:comment]) end - def column_options(o) - column_options = super - column_options[:charset] = o.charset - column_options - end - def add_column_options!(sql, options) if charset = options[:charset] sql << " CHARACTER SET #{charset}" @@ -44,6 +33,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 ce773ed75b..773bbcef4e 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb @@ -3,7 +3,7 @@ module ActiveRecord module MySQL module ColumnMethods def primary_key(name, type = :primary_key, **options) - options[:auto_increment] = true if type == :bigint && !options.key?(:default) + options[:auto_increment] = true if [:integer, :bigint].include?(type) && !options.key?(:default) super end @@ -56,33 +56,25 @@ module ActiveRecord end end - class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition - attr_accessor :charset, :unsigned - end - class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition include ColumnMethods - def new_column_definition(name, type, options) # :nodoc: - column = super - case column.type + def new_column_definition(name, type, **options) # :nodoc: + 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.charset = options[:charset] - column - end - - private - def create_column_definition(name, type) - MySQL::ColumnDefinition.new(name, type) - end + super + end end class Table < ActiveRecord::ConnectionAdapters::Table 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 9b02d8a34b..ad4a069d73 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb @@ -1,21 +1,17 @@ module ActiveRecord module ConnectionAdapters module MySQL - module ColumnDumper - def column_spec_for_primary_key(column) - if column.bigint? - spec = { id: :bigint.inspect } - spec[:default] = schema_default(column) || "nil" unless column.auto_increment? - else - spec = super - 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 @@ -26,7 +22,11 @@ module ActiveRecord private def default_primary_key?(column) - super && column.auto_increment? + super && column.auto_increment? && !column.unsigned? + end + + def explicit_primary_key_default?(column) + column.type == :integer && !column.auto_increment? end def schema_type(column) @@ -48,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.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb index 0e526f6201..4098250f3e 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb @@ -11,6 +11,7 @@ require "active_record/connection_adapters/postgresql/oid/inet" require "active_record/connection_adapters/postgresql/oid/json" require "active_record/connection_adapters/postgresql/oid/jsonb" require "active_record/connection_adapters/postgresql/oid/money" +require "active_record/connection_adapters/postgresql/oid/oid" require "active_record/connection_adapters/postgresql/oid/point" require "active_record/connection_adapters/postgresql/oid/legacy_point" require "active_record/connection_adapters/postgresql/oid/range" 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/bit.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb index 302d393277..0a505f46a7 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb @@ -41,6 +41,8 @@ module ActiveRecord /\A[0-9A-F]*\Z/i.match?(value) 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 :value 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/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/oid.rb new file mode 100644 index 0000000000..9c2ac08b30 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/oid.rb @@ -0,0 +1,13 @@ +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + module OID # :nodoc: + class Oid < Type::Integer # :nodoc: + def type + :oid + end + end + end + end + end +end 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/oid/specialized_string.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb index 2d2fede4e8..564e82a4ac 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb @@ -5,8 +5,9 @@ module ActiveRecord class SpecializedString < Type::String # :nodoc: attr_reader :type - def initialize(type) + def initialize(type, **options) @type = type + super(options) end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb index b5031d890f..6663448a99 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -55,6 +55,10 @@ module ActiveRecord end end + def quoted_binary(value) # :nodoc: + "'#{escape_bytea(value.to_s)}'" + end + def quote_default_expression(value, column) # :nodoc: if value.is_a?(Proc) value.call @@ -76,8 +80,6 @@ module ActiveRecord def _quote(value) case value - when Type::Binary::Data - "'#{escape_bytea(value.to_s)}'" when OID::Xml::Data "xml '#{quote_string(value.to_s)}'" when OID::Bit::Data @@ -92,6 +94,8 @@ module ActiveRecord else super end + when OID::Array::Data + _quote(encode_array(value)) else super end @@ -106,10 +110,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_creation.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb new file mode 100644 index 0000000000..e1d5089115 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb @@ -0,0 +1,15 @@ +module ActiveRecord + module ConnectionAdapters + module PostgreSQL + class SchemaCreation < AbstractAdapter::SchemaCreation # :nodoc: + private + def add_column_options!(sql, options) + if options[:collation] + sql << " COLLATE \"#{options[:collation]}\"" + end + super + 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 ae1ec3e2b7..11ea1e5144 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb @@ -42,7 +42,17 @@ 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[:default] = options.fetch(:default, "gen_random_uuid()") if type == :uuid + 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) + type = if type == :bigint || options[:limit] == 8 + :bigserial + else + :serial + end + end + super end @@ -78,6 +88,10 @@ module ActiveRecord args.each { |name| column(name, :inet, options) } end + def interval(*args, **options) + args.each { |name| column(name, :interval, options) } + end + def int4range(*args, **options) args.each { |name| column(name, :int4range, options) } end @@ -110,6 +124,10 @@ module ActiveRecord args.each { |name| column(name, :numrange, options) } end + def oid(*args, **options) + args.each { |name| column(name, :oid, options) } + end + def point(*args, **options) args.each { |name| column(name, :point, options) } end @@ -163,24 +181,8 @@ module ActiveRecord end end - class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition - attr_accessor :array - end - class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition include ColumnMethods - - def new_column_definition(name, type, options) # :nodoc: - column = super - column.array = options[:array] - column - end - - private - - def create_column_definition(name, type) - PostgreSQL::ColumnDefinition.new name, type - end end class Table < ActiveRecord::ConnectionAdapters::Table 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 c20baf655c..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 @@ -25,7 +17,11 @@ module ActiveRecord private def default_primary_key?(column) - schema_type(column) == :serial + schema_type(column) == :bigserial + end + + def explicit_primary_key_default?(column) + column.type == :uuid || (column.type == :integer && !column.serial?) end def schema_type(column) 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..eebc688686 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -3,22 +3,6 @@ require "active_support/core_ext/string/strip" module ActiveRecord module ConnectionAdapters module PostgreSQL - class SchemaCreation < AbstractAdapter::SchemaCreation - private - - def visit_ColumnDefinition(o) - o.sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale, o.array) - super - end - - def add_column_options!(sql, options) - if options[:collation] - sql << " COLLATE \"#{options[:collation]}\"" - end - super - end - end - module SchemaStatements # Drops the database specified on the +name+ attribute # and creates it again using the provided +options+. @@ -71,13 +55,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 +69,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 @@ -152,7 +132,12 @@ module ActiveRecord end # Verifies existence of an index with a given name. - def index_name_exists?(table_name, index_name, default) + def index_name_exists?(table_name, index_name, default = nil) + unless default.nil? + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Passing default to #index_name_exists? is deprecated without replacement. + MSG + end table = Utils.extract_schema_qualified_name(table_name.to_s) index = Utils.extract_schema_qualified_name(index_name.to_s) @@ -170,7 +155,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") @@ -484,7 +475,7 @@ module ActiveRecord clear_cache! quoted_table_name = quote_table_name(table_name) quoted_column_name = quote_column_name(column_name) - sql_type = type_to_sql(type, options[:limit], options[:precision], options[:scale], options[:array]) + sql_type = type_to_sql(type, options) sql = "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quoted_column_name} TYPE #{sql_type}" if options[:collation] sql << " COLLATE \"#{options[:collation]}\"" @@ -492,7 +483,7 @@ module ActiveRecord if options[:using] sql << " USING #{options[:using]}" elsif options[:cast_as] - cast_as_type = type_to_sql(options[:cast_as], options[:limit], options[:precision], options[:scale], options[:array]) + cast_as_type = type_to_sql(options[:cast_as], options) sql << " USING CAST(#{quoted_column_name} AS #{cast_as_type})" end execute sql @@ -628,7 +619,7 @@ module ActiveRecord end # Maps logical Rails types to PostgreSQL-specific data types. - def type_to_sql(type, limit = nil, precision = nil, scale = nil, array = nil) + def type_to_sql(type, limit: nil, precision: nil, scale: nil, array: nil, **) # :nodoc: sql = \ case type.to_s when "binary" @@ -653,7 +644,7 @@ module ActiveRecord else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a numeric with scale 0 instead.") end else - super(type, limit, precision, scale) + super end sql << "[]" if array && type != :primary_key diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb b/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb index 1412928ca5..a3f9ce6d64 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/utils.rb @@ -35,6 +35,12 @@ module ActiveRecord end protected + + def parts + @parts ||= [@schema, @identifier].compact + end + + private def unquote(part) if part && part.start_with?('"') part[1..-2] @@ -42,10 +48,6 @@ module ActiveRecord part end end - - def parts - @parts ||= [@schema, @identifier].compact - end end module Utils # :nodoc: diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 28e80a084a..5ce61183cd 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -9,6 +9,7 @@ require "active_record/connection_adapters/postgresql/explain_pretty_printer" require "active_record/connection_adapters/postgresql/oid" require "active_record/connection_adapters/postgresql/quoting" require "active_record/connection_adapters/postgresql/referential_integrity" +require "active_record/connection_adapters/postgresql/schema_creation" require "active_record/connection_adapters/postgresql/schema_definitions" require "active_record/connection_adapters/postgresql/schema_dumper" require "active_record/connection_adapters/postgresql/schema_statements" @@ -70,7 +71,7 @@ module ActiveRecord ADAPTER_NAME = "PostgreSQL".freeze NATIVE_DATABASE_TYPES = { - primary_key: "serial primary key", + primary_key: "bigserial primary key", string: { name: "character varying" }, text: { name: "text" }, integer: { name: "integer" }, @@ -108,6 +109,8 @@ module ActiveRecord bit: { name: "bit" }, bit_varying: { name: "bit varying" }, money: { name: "money" }, + interval: { name: "interval" }, + oid: { name: "oid" }, } OID = PostgreSQL::OID #:nodoc: @@ -281,11 +284,6 @@ module ActiveRecord true end - # Does PostgreSQL support finding primary key on non-Active Record tables? - def supports_primary_key? #:nodoc: - true - end - def set_standard_conforming_strings execute("SET standard_conforming_strings = on", "SCHEMA") end @@ -404,10 +402,12 @@ module ActiveRecord @connection.server_version end - protected + private # See http://www.postgresql.org/docs/current/static/errcodes-appendix.html VALUE_LIMIT_VIOLATION = "22001" + NUMERIC_VALUE_OUT_OF_RANGE = "22003" + NOT_NULL_VIOLATION = "23502" FOREIGN_KEY_VIOLATION = "23503" UNIQUE_VIOLATION = "23505" SERIALIZATION_FAILURE = "40001" @@ -423,6 +423,10 @@ module ActiveRecord InvalidForeignKey.new(message) when VALUE_LIMIT_VIOLATION ValueTooLong.new(message) + when NUMERIC_VALUE_OUT_OF_RANGE + RangeError.new(message) + when NOT_NULL_VIOLATION + NotNullViolation.new(message) when SERIALIZATION_FAILURE SerializationFailure.new(message) when DEADLOCK_DETECTED @@ -432,9 +436,7 @@ module ActiveRecord end end - private - - def get_oid_type(oid, fmod, column_name, sql_type = "") # :nodoc: + def get_oid_type(oid, fmod, column_name, sql_type = "".freeze) if !type_map.key?(oid) load_additional_types(type_map, [oid]) end @@ -447,11 +449,11 @@ module ActiveRecord } end - def initialize_type_map(m) # :nodoc: + def initialize_type_map(m) register_class_with_limit m, "int2", Type::Integer register_class_with_limit m, "int4", Type::Integer register_class_with_limit m, "int8", Type::Integer - m.alias_type "oid", "int2" + m.register_type "oid", OID::Oid.new m.register_type "float4", Type::Float.new m.alias_type "float8", "float4" m.register_type "text", Type::Text.new @@ -486,8 +488,10 @@ module ActiveRecord m.register_type "polygon", OID::SpecializedString.new(:polygon) m.register_type "circle", OID::SpecializedString.new(:circle) - # FIXME: why are we keeping these types as strings? - m.alias_type "interval", "varchar" + m.register_type "interval" do |_, _, sql_type| + precision = extract_precision(sql_type) + OID::SpecializedString.new(:interval, precision: precision) + end register_class_with_precision m, "time", Type::Time register_class_with_precision m, "timestamp", OID::DateTime @@ -515,7 +519,7 @@ module ActiveRecord load_additional_types(m) end - def extract_limit(sql_type) # :nodoc: + def extract_limit(sql_type) case sql_type when /^bigint/i, /^int8/i 8 @@ -527,7 +531,7 @@ module ActiveRecord end # Extracts the value from a PostgreSQL column default definition. - def extract_value_from_default(default) # :nodoc: + def extract_value_from_default(default) case default # Quoted types when /\A[\(B]?'(.*)'.*::"?([\w. ]+)"?(?:\[\])?\z/m @@ -553,15 +557,15 @@ module ActiveRecord end end - def extract_default_function(default_value, default) # :nodoc: + def extract_default_function(default_value, default) default if has_default_function?(default_value, default) end - def has_default_function?(default_value, default) # :nodoc: + def has_default_function?(default_value, default) !default_value && (%r{\w+\(.*\)|\(.*\)::\w+} === default) end - def load_additional_types(type_map, oids = nil) # :nodoc: + def load_additional_types(type_map, oids = nil) initializer = OID::TypeMapInitializer.new(type_map) if supports_ranges? @@ -729,7 +733,7 @@ module ActiveRecord end # Returns the current ID of a table's sequence. - def last_insert_id_result(sequence_name) # :nodoc: + def last_insert_id_result(sequence_name) exec_query("SELECT currval('#{sequence_name}')", "SQL") end @@ -751,27 +755,27 @@ module ActiveRecord # Query implementation notes: # - format_type includes the column size constraint, e.g. varchar(50) # - ::regclass is a function that gives the id for a table name - def column_definitions(table_name) # :nodoc: + def column_definitions(table_name) 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 end_sql end - def extract_table_ref_from_insert_sql(sql) # :nodoc: + def extract_table_ref_from_insert_sql(sql) sql[/into\s("[A-Za-z0-9_."\[\]\s]+"|[A-Za-z0-9_."\[\]]+)\s*/im] $1.strip if $1 end - def create_table_definition(*args) # :nodoc: + def create_table_definition(*args) PostgreSQL::TableDefinition.new(*args) end diff --git a/activerecord/lib/active_record/connection_adapters/schema_cache.rb b/activerecord/lib/active_record/connection_adapters/schema_cache.rb index 8219f132c3..4d339b0a8c 100644 --- a/activerecord/lib/active_record/connection_adapters/schema_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/schema_cache.rb @@ -21,6 +21,22 @@ module ActiveRecord @data_sources = @data_sources.dup end + def encode_with(coder) + coder["columns"] = @columns + coder["columns_hash"] = @columns_hash + coder["primary_keys"] = @primary_keys + coder["data_sources"] = @data_sources + coder["version"] = ActiveRecord::Migrator.current_version + end + + def init_with(coder) + @columns = coder["columns"] + @columns_hash = coder["columns_hash"] + @primary_keys = coder["primary_keys"] + @data_sources = coder["data_sources"] + @version = coder["version"] + end + def primary_keys(table_name) @primary_keys[table_name] ||= data_source_exists?(table_name) ? connection.primary_key(table_name) : nil end @@ -32,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) @@ -47,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) @@ -83,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/quoting.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb index f01ed67b0f..7276a65098 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb @@ -18,15 +18,11 @@ module ActiveRecord quoted_date(value) end - private + def quoted_binary(value) + "x'#{value.hex}'" + end - def _quote(value) - if value.is_a?(Type::Binary::Data) - "x'#{value.hex}'" - else - super - end - end + private def _type_cast(value) case value diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb index 70c0d28830..bc798d1dbb 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb @@ -1,15 +1,8 @@ module ActiveRecord module ConnectionAdapters module SQLite3 - class SchemaCreation < AbstractAdapter::SchemaCreation + class SchemaCreation < AbstractAdapter::SchemaCreation # :nodoc: private - - def column_options(o) - options = super - options[:null] = false if o.primary_key - options - end - def add_column_options!(sql, options) if options[:collation] sql << " COLLATE \"#{options[:collation]}\"" diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb new file mode 100644 index 0000000000..e157e4b218 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb @@ -0,0 +1,28 @@ +module ActiveRecord + module ConnectionAdapters + module SQLite3 + module ColumnMethods + def primary_key(name, type = :primary_key, **options) + if %i(integer bigint).include?(type) && (options.delete(:auto_increment) == true || !options.key?(:default)) + type = :primary_key + end + + super + end + end + + class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition + include ColumnMethods + + def references(*args, **options) + super(*args, type: :integer, **options) + end + alias :belongs_to :references + end + + class Table < ActiveRecord::ConnectionAdapters::Table + include ColumnMethods + end + end + end +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 new file mode 100644 index 0000000000..eec018eda3 --- /dev/null +++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb @@ -0,0 +1,17 @@ +module ActiveRecord + module ConnectionAdapters + module SQLite3 + 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 +end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index 0493ab4e4b..16ef195bfc 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -3,6 +3,8 @@ require "active_record/connection_adapters/statement_pool" require "active_record/connection_adapters/sqlite3/explain_pretty_printer" require "active_record/connection_adapters/sqlite3/quoting" require "active_record/connection_adapters/sqlite3/schema_creation" +require "active_record/connection_adapters/sqlite3/schema_definitions" +require "active_record/connection_adapters/sqlite3/schema_dumper" gem "sqlite3", "~> 1.3.6" require "sqlite3" @@ -52,6 +54,7 @@ module ActiveRecord ADAPTER_NAME = "SQLite".freeze include SQLite3::Quoting + include SQLite3::ColumnDumper NATIVE_DATABASE_TYPES = { primary_key: "INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL", @@ -75,6 +78,10 @@ module ActiveRecord end end + def update_table_definition(table_name, base) # :nodoc: + SQLite3::Table.new(table_name, base) + end + def schema_creation # :nodoc: SQLite3::SchemaCreation.new self end @@ -88,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? @@ -113,12 +122,12 @@ module ActiveRecord true end - def supports_primary_key? #:nodoc: + def requires_reloading? true end - def requires_reloading? - true + def supports_foreign_keys_in_create? + sqlite_version >= "3.6.19" end def supports_views? @@ -178,6 +187,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 ====================================== #++ @@ -252,47 +274,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: @@ -322,6 +331,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 @@ -424,16 +439,34 @@ module ActiveRecord rename_column_indexes(table_name, column.name, new_column_name) end - protected + def add_reference(table_name, ref_name, **options) # :nodoc: + super(table_name, ref_name, type: :integer, **options) + end + alias :add_belongs_to :add_reference + + 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) # :nodoc: + def table_structure(table_name) structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA") raise(ActiveRecord::StatementInvalid, "Could not find table '#{table_name}'") if structure.empty? table_structure_with_collation(table_name, structure) end alias column_definitions table_structure - def alter_table(table_name, options = {}) #:nodoc: + def alter_table(table_name, options = {}) altered_table_name = "a#{table_name}" caller = lambda { |definition| yield definition if block_given? } @@ -444,12 +477,12 @@ module ActiveRecord end end - def move_table(from, to, options = {}, &block) #:nodoc: + def move_table(from, to, options = {}, &block) copy_table(from, to, options, &block) drop_table(from) end - def copy_table(from, to, options = {}) #:nodoc: + def copy_table(from, to, options = {}) from_primary_key = primary_key(from) options[:id] = false create_table(to, options) do |definition| @@ -475,7 +508,7 @@ module ActiveRecord options[:rename] || {}) end - def copy_table_indexes(from, to, rename = {}) #:nodoc: + def copy_table_indexes(from, to, rename = {}) indexes(from).each do |index| name = index.name if to == "a#{from}" @@ -498,7 +531,7 @@ module ActiveRecord end end - def copy_table_contents(from, to, columns, rename = {}) #:nodoc: + def copy_table_contents(from, to, columns, rename = {}) column_mappings = Hash[columns.map { |name| [name, name] }] rename.each { |a| column_mappings[a.last] = a.first } from_columns = columns(from).collect(&:name) @@ -523,12 +556,15 @@ module ActiveRecord # column *column_name* is not unique when /column(s)? .* (is|are) not unique/, /UNIQUE constraint failed: .*/ 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 end - private COLLATE_REGEX = /.*\"(\w+)\".*collate\s+\"(\w+)\".*/i.freeze def table_structure_with_collation(table_name, basic_structure) @@ -569,6 +605,22 @@ module ActiveRecord basic_structure.to_hash end end + + 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 1fbe374ade..0028dc0edb 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -171,41 +171,37 @@ 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}", name, primary_key, id) end record - rescue RangeError + rescue ::RangeError raise RecordNotFound.new("Couldn't find #{name} with an out of range value for '#{primary_key}'", name, primary_key) 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 @@ -223,7 +219,7 @@ module ActiveRecord statement.execute(hash.values, self, connection).first rescue TypeError raise ActiveRecord::StatementInvalid - rescue RangeError + rescue ::RangeError nil end end @@ -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 @@ -299,24 +297,24 @@ module ActiveRecord private - def cached_find_by_statement(key, &block) # :nodoc: + def cached_find_by_statement(key, &block) cache = @find_by_statement_cache[connection.prepared_statements] cache[key] || cache.synchronize { cache[key] ||= StatementCache.create(connection, &block) } end - def relation # :nodoc: + def relation 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 end - def table_metadata # :nodoc: + def table_metadata TableMetadata.new(self, arel_table) end 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/enum.rb b/activerecord/lib/active_record/enum.rb index 0a94ab58dd..0ab03b2ab3 100644 --- a/activerecord/lib/active_record/enum.rb +++ b/activerecord/lib/active_record/enum.rb @@ -140,6 +140,8 @@ module ActiveRecord end 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 :name, :mapping, :subtype diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index 6464d40c94..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. @@ -123,10 +113,46 @@ module ActiveRecord class InvalidForeignKey < WrappedDatabaseException end + # Raised when a foreign key constraint cannot be added because the column type does not match the referenced column type. + class MismatchedForeignKey < StatementInvalid + def initialize(adapter = nil, message: nil, table: nil, foreign_key: nil, target_table: nil, primary_key: nil) + @adapter = adapter + if table + msg = <<-EOM.strip_heredoc + Column `#{foreign_key}` on table `#{table}` has a type of `#{column_type(table, foreign_key)}`. + This does not match column `#{primary_key}` on `#{target_table}`, which has type `#{column_type(target_table, primary_key)}`. + To resolve this issue, change the type of the `#{foreign_key}` column on `#{table}` to be :integer. (For example `t.integer #{foreign_key}`). + EOM + else + msg = <<-EOM + There is a mismatch between the foreign key and primary key column types. + Verify that the foreign key column type and the primary key of the associated table match types. + EOM + end + if message + msg << "\nOriginal message: #{message}" + end + super(msg) + end + + private + def column_type(table, column) + @adapter.columns(table).detect { |c| c.name == column }.sql_type + end + end + + # Raised when a record cannot be inserted or updated because it would violate a not null constraint. + class NotNullViolation < StatementInvalid + end + # Raised when a record cannot be inserted or updated because a value too long for a column type. class ValueTooLong < StatementInvalid end + # Raised when values that executed are out of range. + class RangeError < StatementInvalid + end + # Raised when number of bind variables in statement given to +:condition+ key # (for example, when using {ActiveRecord::Base.find}[rdoc-ref:FinderMethods#find] method) # does not match number of expected values supplied. diff --git a/activerecord/lib/active_record/fixture_set/file.rb b/activerecord/lib/active_record/fixture_set/file.rb index 5ba354d758..6cf2e01179 100644 --- a/activerecord/lib/active_record/fixture_set/file.rb +++ b/activerecord/lib/active_record/fixture_set/file.rb @@ -66,10 +66,13 @@ module ActiveRecord # Validate our unmarshalled data. def validate(data) unless Hash === data || YAML::Omap === data - raise Fixture::FormatError, "fixture is not a hash" + raise Fixture::FormatError, "fixture is not a hash: #{@file}" end - raise Fixture::FormatError unless data.all? { |name, row| Hash === row } + invalid = data.reject { |_, row| Hash === row } + if invalid.any? + raise Fixture::FormatError, "fixture key is not a hash: #{@file}, keys: #{invalid.keys.inspect}" + end data end end diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 21c5e5b5bb..91d8054ef2 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -536,16 +536,16 @@ module ActiveRecord update_all_loaded_fixtures fixtures_map connection.transaction(requires_new: true) do - deleted_tables = Set.new + deleted_tables = Hash.new { |h, k| h[k] = Set.new } fixture_sets.each do |fs| conn = fs.model_class.respond_to?(:connection) ? fs.model_class.connection : connection table_rows = fs.table_rows table_rows.each_key do |table| - unless deleted_tables.include? table + unless deleted_tables[conn].include? table conn.delete "DELETE FROM #{conn.quote_table_name(table)}", "Fixture Delete" end - deleted_tables << table + deleted_tables[conn] << table end table_rows.each do |fixture_set_name, rows| @@ -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/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index 82882469e3..2659c60f1f 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -69,7 +69,7 @@ module ActiveRecord send(lock_col + "=", previous_lock_value + 1) end - def _create_record(attribute_names = self.attribute_names, *) # :nodoc: + def _create_record(attribute_names = self.attribute_names, *) if locking_enabled? # We always want to persist the locking version, even if we don't detect # a change from the default, since the database might have no default @@ -78,7 +78,7 @@ module ActiveRecord super end - def _update_record(attribute_names = self.attribute_names) #:nodoc: + def _update_record(attribute_names = self.attribute_names) return super unless locking_enabled? lock_col = self.class.locking_column diff --git a/activerecord/lib/active_record/locking/pessimistic.rb b/activerecord/lib/active_record/locking/pessimistic.rb index e73cb4fc12..263e2a5f7f 100644 --- a/activerecord/lib/active_record/locking/pessimistic.rb +++ b/activerecord/lib/active_record/locking/pessimistic.rb @@ -59,7 +59,16 @@ module ActiveRecord # or pass true for "FOR UPDATE" (the default, an exclusive row lock). Returns # the locked record. def lock!(lock = true) - reload(lock: lock) if persisted? + if persisted? + if changed? + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Locking a record with unpersisted changes is deprecated and will raise an + exception in Rails 5.2. Use `save` to persist the changes, or `reload` to + discard them explicitly. + MSG + end + reload(lock: lock) + end self end 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 ae45ac7157..85032ce470 100644 --- a/activerecord/lib/active_record/migration/compatibility.rb +++ b/activerecord/lib/active_record/migration/compatibility.rb @@ -13,7 +13,66 @@ module ActiveRecord V5_1 = Current - module FourTwoShared + class V5_0 < V5_1 + module TableDefinition + def references(*args, **options) + super(*args, type: :integer, **options) + end + alias :belongs_to :references + end + + 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 + + if block_given? + super(table_name, options) do |t| + class << t + prepend TableDefinition + end + yield t + end + else + super + end + end + + def change_table(table_name, options = {}) + if block_given? + super(table_name, options) do |t| + class << t + prepend TableDefinition + end + yield t + end + else + super + end + end + + def add_reference(table_name, ref_name, **options) + super(table_name, ref_name, type: :integer, **options) + end + alias :add_belongs_to :add_reference + end + + class V4_2 < V5_0 module TableDefinition def references(*, **options) options[:index] ||= false @@ -86,13 +145,13 @@ module ActiveRecord def index_name_for_remove(table_name, options = {}) index_name = index_name(table_name, options) - unless index_name_exists?(table_name, index_name, true) + unless index_name_exists?(table_name, index_name) if options.is_a?(Hash) && options.has_key?(:name) options_without_column = options.dup options_without_column.delete :column index_name_without_column = index_name(table_name, options_without_column) - return index_name_without_column if index_name_exists?(table_name, index_name_without_column, false) + return index_name_without_column if index_name_exists?(table_name, index_name_without_column) end raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist" @@ -101,37 +160,6 @@ module ActiveRecord index_name end end - - class V5_0 < V5_1 - def create_table(table_name, options = {}) - if ActiveRecord::Base.connection.adapter_name == "PostgreSQL" - if options[:id] == :uuid && !options[:default] - options[:default] = "uuid_generate_v4()" - end - 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/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index 2a28c6bf6d..54216caaaf 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -432,6 +432,7 @@ module ActiveRecord connection.schema_cache.clear_data_source_cache!(table_name) reload_schema_from_cache + initialize_find_by_cache end private 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 8e13ee3564..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 @@ -181,7 +183,11 @@ module ActiveRecord _raise_readonly_record_error if readonly? destroy_associations self.class.connection.add_transaction_record(self) - destroy_row if persisted? + @_trigger_destroy_callback = if persisted? + destroy_row > 0 + else + true + end @destroyed = true freeze end @@ -334,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 @@ -353,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 @@ -388,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> @@ -519,6 +529,7 @@ module ActiveRecord raise ActiveRecord::StaleObjectError.new(self, "touch") end + @_trigger_update_callback = result result else true @@ -550,10 +561,13 @@ module ActiveRecord def _update_record(attribute_names = self.attribute_names) attributes_values = arel_attributes_with_values_for_update(attribute_names) if attributes_values.empty? - 0 + rows_affected = 0 + @_trigger_update_callback = true else - self.class.unscoped._update_record attributes_values, id, id_in_database + rows_affected = self.class.unscoped._update_record attributes_values, id, id_in_database + @_trigger_update_callback = rows_affected > 0 end + rows_affected end # Creates a record with values matching those of the instance attributes diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index 7ce10df6d4..0276d41494 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -82,15 +82,15 @@ module ActiveRecord if config.active_record.delete(:use_schema_cache_dump) config.after_initialize do |app| ActiveSupport.on_load(:active_record) do - filename = File.join(app.config.paths["db"].first, "schema_cache.dump") + filename = File.join(app.config.paths["db"].first, "schema_cache.yml") if File.file?(filename) - cache = Marshal.load File.binread 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.dump because it has expired. The current schema version is #{ActiveRecord::Migrator.current_version}, but the one in the cache is #{cache.version}." + 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 end end diff --git a/activerecord/lib/active_record/railties/controller_runtime.rb b/activerecord/lib/active_record/railties/controller_runtime.rb index adb3c6c4e6..8658188623 100644 --- a/activerecord/lib/active_record/railties/controller_runtime.rb +++ b/activerecord/lib/active_record/railties/controller_runtime.rb @@ -6,10 +6,14 @@ module ActiveRecord module ControllerRuntime #:nodoc: extend ActiveSupport::Concern + # TODO Change this to private once we've dropped Ruby 2.2 support. + # Workaround for Ruby 2.2 "private attribute?" warning. protected attr_internal :db_runtime + private + def process_action(action, *args) # We also need to reset the runtime before each action # because of queries in middleware or in cases we are streaming diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 46235ab922..246d330b76 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -265,19 +265,16 @@ db_namespace = namespace :db do end namespace :cache do - desc "Creates a db/schema_cache.dump file." + desc "Creates a db/schema_cache.yml file." task dump: [:environment, :load_config] do - con = ActiveRecord::Base.connection - filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.dump") - - con.schema_cache.clear! - con.data_sources.each { |table| con.schema_cache.add(table) } - open(filename, "wb") { |f| f.write(Marshal.dump(con.schema_cache)) } + conn = ActiveRecord::Base.connection + filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.yml") + ActiveRecord::Tasks::DatabaseTasks.dump_schema_cache(conn, filename) end - desc "Clears a db/schema_cache.dump file." + desc "Clears a db/schema_cache.yml file." task clear: [:environment, :load_config] do - filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.dump") + filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.yml") rm_f filename, verbose: false end end @@ -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 17751c9571..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? @@ -850,6 +834,10 @@ module ActiveRecord actual_source_reflection.options[:primary_key] || primary_key(klass || self.klass) end + def association_primary_key_type + 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. # # class Post < ActiveRecord::Base @@ -874,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 @@ -929,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 @@ -959,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 @@ -971,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 @@ -989,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..35c670f1a1 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 diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb index 4b9310b225..d3ba724507 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 @@ -35,6 +38,7 @@ module ActiveRecord delegate :to_xml, :encode_with, :length, :collect, :map, :each, :all?, :include?, :to_ary, :join, :[], :&, :|, :+, :-, :sample, :reverse, :compact, :in_groups, :in_groups_of, + :to_sentence, :to_formatted_s, :shuffle, :split, :index, to: :records delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key, @@ -78,7 +82,7 @@ module ActiveRecord end end - protected + private def method_missing(method, *args, &block) if @klass.respond_to?(method) @@ -110,7 +114,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 93c8722aa3..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 # @@ -76,7 +76,7 @@ module ActiveRecord # Post.find_by "published_at < ?", 2.weeks.ago def find_by(arg, *args) where(arg, *args).take - rescue RangeError + rescue ::RangeError nil end @@ -84,7 +84,7 @@ module ActiveRecord # an ActiveRecord::RecordNotFound error. def find_by!(arg, *args) where(arg, *args).take! - rescue RangeError + rescue ::RangeError raise RecordNotFound.new("Couldn't find #{@klass.name} with an out of range value", @klass.name) end @@ -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 @@ -333,7 +324,7 @@ module ActiveRecord end connection.select_value(relation, "#{name} Exists", relation.bound_attributes) ? true : false - rescue RangeError + rescue ::RangeError false end @@ -439,143 +430,140 @@ module ActiveRecord reflections.none?(&:collection?) end - protected + private - def find_with_ids(*ids) - 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) - 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) - 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) - 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 - 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) - 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) - @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) - 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) - 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 - - private - 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 780a1ee422..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) @@ -66,6 +64,8 @@ module ActiveRecord handler_for(value).call(attribute, value) 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 :table diff --git a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb index 6400caba06..88b6c37d43 100644 --- a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb +++ b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb @@ -29,6 +29,8 @@ module ActiveRecord array_predicates.inject { |composite, predicate| composite.or(predicate) } 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 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 7e20cb2c63..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 @@ -28,6 +28,8 @@ module ActiveRecord predicate_builder.build_from_hash(queries) 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 @@ -68,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/base_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/base_handler.rb index 65c5159704..3bb1037885 100644 --- a/activerecord/lib/active_record/relation/predicate_builder/base_handler.rb +++ b/activerecord/lib/active_record/relation/predicate_builder/base_handler.rb @@ -9,6 +9,8 @@ module ActiveRecord predicate_builder.build(attribute, value.id) 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 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 0a6574fcf1..0000000000 --- a/activerecord/lib/active_record/relation/predicate_builder/class_handler.rb +++ /dev/null @@ -1,27 +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 - - 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/predicate_builder/polymorphic_array_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb index 0c7f92b3d0..335124c952 100644 --- a/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +++ b/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb @@ -21,6 +21,8 @@ module ActiveRecord end 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 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/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index 190e339ea8..ada89b5ec3 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -66,7 +66,7 @@ module ActiveRecord private - def relation_with(values) # :nodoc: + def relation_with(values) result = Relation.create(klass, table, predicate_builder, values) result.extend(*extending_values) if extending_values.any? result diff --git a/activerecord/lib/active_record/relation/where_clause.rb b/activerecord/lib/active_record/relation/where_clause.rb index e550fcf7ae..417b24c7bb 100644 --- a/activerecord/lib/active_record/relation/where_clause.rb +++ b/activerecord/lib/active_record/relation/where_clause.rb @@ -81,6 +81,8 @@ module ActiveRecord @empty ||= new([], []) 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 :predicates diff --git a/activerecord/lib/active_record/relation/where_clause_factory.rb b/activerecord/lib/active_record/relation/where_clause_factory.rb index 1e7deeffad..04bee73e8f 100644 --- a/activerecord/lib/active_record/relation/where_clause_factory.rb +++ b/activerecord/lib/active_record/relation/where_clause_factory.rb @@ -15,9 +15,12 @@ module ActiveRecord attributes = klass.send(:expand_hash_conditions_for_aggregates, attributes) attributes.stringify_keys! - attributes, binds = predicate_builder.create_binds(attributes) - - parts = predicate_builder.build_from_hash(attributes) + if perform_case_sensitive?(options = other.last) + parts, binds = build_for_case_sensitive(attributes, options) + else + attributes, binds = predicate_builder.create_binds(attributes) + parts = predicate_builder.build_from_hash(attributes) + end when Arel::Nodes::Node parts = [opts] else @@ -27,9 +30,48 @@ module ActiveRecord WhereClause.new(parts, binds || []) 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 :klass, :predicate_builder + + private + + def perform_case_sensitive?(options) + options && options.key?(:case_sensitive) + end + + def build_for_case_sensitive(attributes, options) + parts, binds = [], [] + table = klass.arel_table + + attributes.each do |attribute, value| + if reflection = klass._reflect_on_association(attribute) + attribute = reflection.foreign_key.to_s + value = value[reflection.klass.primary_key] unless value.nil? + end + + if value.nil? + parts << table[attribute].eq(value) + else + column = klass.column_for_attribute(attribute) + + binds << predicate_builder.send(:build_bind_param, attribute, value) + value = Arel::Nodes::BindParam.new + + predicate = if options[:case_sensitive] + klass.connection.case_sensitive_comparison(table, attribute, column, value) + else + klass.connection.case_insensitive_comparison(table, attribute, column, value) + end + + parts << predicate + end + end + + [parts, binds] + end end end end diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb index 3d52dc44cf..427c0019c6 100644 --- a/activerecord/lib/active_record/sanitization.rb +++ b/activerecord/lib/active_record/sanitization.rb @@ -4,7 +4,7 @@ module ActiveRecord extend ActiveSupport::Concern module ClassMethods - protected + private # Accepts an array or string of SQL conditions and sanitizes # them into a valid SQL fragment for a WHERE clause. @@ -20,7 +20,7 @@ module ActiveRecord # # sanitize_sql_for_conditions("name='foo''bar' and group_id='4'") # # => "name='foo''bar' and group_id='4'" - def sanitize_sql_for_conditions(condition) + def sanitize_sql_for_conditions(condition) # :doc: return nil if condition.blank? case condition @@ -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) + 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) @@ -62,7 +62,7 @@ module ActiveRecord # # sanitize_sql_for_order("id ASC") # # => "id ASC" - def sanitize_sql_for_order(condition) + def sanitize_sql_for_order(condition) # :doc: if condition.is_a?(Array) && condition.first.to_s.include?("?") sanitize_sql_array(condition) else @@ -85,7 +85,7 @@ module ActiveRecord # # { address: Address.new("813 abc st.", "chicago") } # # => { address_street: "813 abc st.", address_city: "chicago" } - def expand_hash_conditions_for_aggregates(attrs) + def expand_hash_conditions_for_aggregates(attrs) # :doc: expanded_attrs = {} attrs.each do |attr, value| if aggregation = reflect_on_aggregation(attr.to_sym) @@ -108,7 +108,7 @@ module ActiveRecord # # sanitize_sql_hash_for_assignment({ status: nil, group_id: 1 }, "posts") # # => "`posts`.`status` = NULL, `posts`.`group_id` = 1" - def sanitize_sql_hash_for_assignment(attrs, table) + def sanitize_sql_hash_for_assignment(attrs, table) # :doc: c = connection attrs.map do |attr, value| value = type_for_attribute(attr.to_s).serialize(value) @@ -130,7 +130,7 @@ module ActiveRecord # # sanitize_sql_like("snake_cased_string", "!") # # => "snake!_cased!_string" - def sanitize_sql_like(string, escape_character = "\\") + def sanitize_sql_like(string, escape_character = "\\") # :doc: pattern = Regexp.union(escape_character, "%", "_") string.gsub(pattern) { |x| [escape_character, x].join } end @@ -146,7 +146,7 @@ module ActiveRecord # # sanitize_sql_array(["name='%s' and group_id='%s'", "foo'bar", 4]) # # => "name='foo''bar' and group_id='4'" - def sanitize_sql_array(ary) + def sanitize_sql_array(ary) # :doc: statement, *values = ary if values.first.is_a?(Hash) && /:\w+/.match?(statement) replace_named_bind_variables(statement, values.first) @@ -159,7 +159,7 @@ module ActiveRecord end end - def replace_bind_variables(statement, values) # :nodoc: + def replace_bind_variables(statement, values) raise_if_bind_arity_mismatch(statement, statement.count("?"), values.size) bound = values.dup c = connection @@ -168,7 +168,7 @@ module ActiveRecord end end - def replace_bind_variable(value, c = connection) # :nodoc: + def replace_bind_variable(value, c = connection) if ActiveRecord::Relation === value value.to_sql else @@ -176,7 +176,7 @@ module ActiveRecord end end - def replace_named_bind_variables(statement, bind_vars) # :nodoc: + def replace_named_bind_variables(statement, bind_vars) statement.gsub(/(:?):([a-zA-Z]\w*)/) do |match| if $1 == ":" # skip postgresql casts match # return the whole match @@ -188,7 +188,7 @@ module ActiveRecord end end - def quote_bound_value(value, c = connection) # :nodoc: + def quote_bound_value(value, c = connection) if value.respond_to?(:map) && !value.acts_like?(:string) if value.respond_to?(:empty?) && value.empty? c.quote(nil) @@ -200,7 +200,7 @@ module ActiveRecord end end - def raise_if_bind_arity_mismatch(statement, expected, provided) # :nodoc: + def raise_if_bind_arity_mismatch(statement, expected, provided) unless expected == provided raise PreparedStatementInvalid, "wrong number of bind variables (#{provided} for #{expected}) in: #{statement}" end diff --git a/activerecord/lib/active_record/schema.rb b/activerecord/lib/active_record/schema.rb index 99e54a8b24..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 @@ -61,7 +61,7 @@ module ActiveRecord # # ActiveRecord::Schema.new.migrations_paths # # => ["db/migrate"] # Rails migration path by default. - def migrations_paths # :nodoc: + def migrations_paths ActiveRecord::Migrator.migrations_paths end 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/default.rb b/activerecord/lib/active_record/scoping/default.rb index 9d8253faa3..2daa48859a 100644 --- a/activerecord/lib/active_record/scoping/default.rb +++ b/activerecord/lib/active_record/scoping/default.rb @@ -44,7 +44,7 @@ module ActiveRecord self.current_scope = nil end - protected + private # Use this macro in your model to set a default scope for all operations on # the model. @@ -87,7 +87,7 @@ module ActiveRecord # # Should return a scope, you can call 'super' here etc. # end # end - def default_scope(scope = nil) + def default_scope(scope = nil) # :doc: scope = Proc.new if block_given? if scope.is_a?(Relation) || !scope.respond_to?(:call) @@ -101,7 +101,7 @@ module ActiveRecord self.default_scopes += [scope] end - def build_default_scope(base_rel = nil) # :nodoc: + def build_default_scope(base_rel = nil) return if abstract_class? if default_scope_override.nil? @@ -122,18 +122,18 @@ module ActiveRecord end end - def ignore_default_scope? # :nodoc: + def ignore_default_scope? ScopeRegistry.value_for(:ignore_default_scope, base_class) end - def ignore_default_scope=(ignore) # :nodoc: + def ignore_default_scope=(ignore) ScopeRegistry.set_value_for(:ignore_default_scope, base_class, ignore) end # The ignore_default_scope flag is used to prevent an infinite recursion # situation where a default scope references a scope which has a default # scope which references a scope... - def evaluate_default_scope # :nodoc: + def evaluate_default_scope return if ignore_default_scope? begin diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb index 6af84c1266..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 - protected + private - def valid_scope_name?(name) - 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/serialization.rb b/activerecord/lib/active_record/serialization.rb index 5a408e7b8e..db2bd0b55e 100644 --- a/activerecord/lib/active_record/serialization.rb +++ b/activerecord/lib/active_record/serialization.rb @@ -9,7 +9,7 @@ module ActiveRecord #:nodoc: end def serializable_hash(options = nil) - options = options.try(:clone) || {} + options = options.try(:dup) || {} options[:except] = Array(options[:except]).map(&:to_s) options[:except] |= Array(self.class.inheritance_column) diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb index 066573192e..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 @@ -121,18 +121,17 @@ module ActiveRecord end end - protected - def read_store_attribute(store_attribute, key) + private + def read_store_attribute(store_attribute, key) # :doc: accessor = store_accessor_for(store_attribute) accessor.read(self, store_attribute, key) end - def write_store_attribute(store_attribute, key, value) + def write_store_attribute(store_attribute, key, value) # :doc: accessor = store_accessor_for(store_attribute) accessor.write(self, store_attribute, key, value) end - private def store_accessor_for(store_attribute) type_for_attribute(store_attribute.to_s).accessor end @@ -178,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 58184f3872..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 @@ -64,6 +64,8 @@ module ActiveRecord association && association.polymorphic? 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 :klass, :arel_table, :association 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 af3fc88282..08417aaa0f 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -11,7 +11,6 @@ module ActiveRecord :before_commit_without_transaction_enrollment, :commit_without_transaction_enrollment, :rollback_without_transaction_enrollment, - terminator: deprecated_false_terminator, scope: [:kind, :name] end @@ -274,16 +273,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 = {}) @@ -407,10 +396,10 @@ module ActiveRecord end end - protected + private # Save the new record state and id of a record so it can be restored later if a transaction fails. - def remember_transaction_record_state #:nodoc: + def remember_transaction_record_state @_start_transaction_state[:id] = id @_start_transaction_state.reverse_merge!( new_record: @new_record, @@ -421,18 +410,18 @@ module ActiveRecord end # Clear the new record state and id of a record. - def clear_transaction_record_state #:nodoc: + def clear_transaction_record_state @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1 force_clear_transaction_record_state if @_start_transaction_state[:level] < 1 end # Force to clear the transaction record state. - def force_clear_transaction_record_state #:nodoc: + def force_clear_transaction_record_state @_start_transaction_state.clear end # Restore the new record state and id of a record that was previously saved by a call to save_record_state. - def restore_transaction_record_state(force = false) #:nodoc: + def restore_transaction_record_state(force = false) unless @_start_transaction_state.empty? transaction_level = (@_start_transaction_state[:level] || 0) - 1 if transaction_level < 1 || force @@ -450,31 +439,30 @@ module ActiveRecord end # Determine if a record was created or destroyed in a transaction. State should be one of :new_record or :destroyed. - def transaction_record_state(state) #:nodoc: + def transaction_record_state(state) @_start_transaction_state[state] end # Determine if a transaction included an action for :create, :update, or :destroy. Used in filtering callbacks. - def transaction_include_any_action?(actions) #:nodoc: + def transaction_include_any_action?(actions) actions.any? do |action| case action when :create transaction_record_state(:new_record) when :destroy - destroyed? + defined?(@_trigger_destroy_callback) && @_trigger_destroy_callback when :update - !(transaction_record_state(:new_record) || destroyed?) + !(transaction_record_state(:new_record) || destroyed?) && + (defined?(@_trigger_update_callback) && @_trigger_update_callback) end end end - private - - def set_transaction_state(state) # :nodoc: + def set_transaction_state(state) @transaction_state = state end - def has_transactional_callbacks? # :nodoc: + def has_transactional_callbacks? !_rollback_callbacks.empty? || !_commit_callbacks.empty? || !_before_commit_callbacks.empty? end diff --git a/activerecord/lib/active_record/type.rb b/activerecord/lib/active_record/type.rb index 0b48d2186a..4f632660a8 100644 --- a/activerecord/lib/active_record/type.rb +++ b/activerecord/lib/active_record/type.rb @@ -5,7 +5,10 @@ require "active_record/type/internal/timezone" require "active_record/type/date" require "active_record/type/date_time" +require "active_record/type/decimal_without_scale" require "active_record/type/time" +require "active_record/type/text" +require "active_record/type/unsigned_integer" require "active_record/type/serialized" require "active_record/type/adapter_specific_registry" @@ -53,12 +56,9 @@ module ActiveRecord Binary = ActiveModel::Type::Binary Boolean = ActiveModel::Type::Boolean Decimal = ActiveModel::Type::Decimal - DecimalWithoutScale = ActiveModel::Type::DecimalWithoutScale Float = ActiveModel::Type::Float Integer = ActiveModel::Type::Integer String = ActiveModel::Type::String - Text = ActiveModel::Type::Text - UnsignedInteger = ActiveModel::Type::UnsignedInteger Value = ActiveModel::Type::Value register(:big_integer, Type::BigInteger, override: false) diff --git a/activerecord/lib/active_record/type/adapter_specific_registry.rb b/activerecord/lib/active_record/type/adapter_specific_registry.rb index d0f9581576..7cc866f7a7 100644 --- a/activerecord/lib/active_record/type/adapter_specific_registry.rb +++ b/activerecord/lib/active_record/type/adapter_specific_registry.rb @@ -50,6 +50,8 @@ module ActiveRecord priority <=> other.priority 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 :name, :block, :adapter, :override @@ -110,6 +112,8 @@ module ActiveRecord super | 4 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 :options, :klass diff --git a/activerecord/lib/active_record/type/decimal_without_scale.rb b/activerecord/lib/active_record/type/decimal_without_scale.rb new file mode 100644 index 0000000000..7ce33e9cd3 --- /dev/null +++ b/activerecord/lib/active_record/type/decimal_without_scale.rb @@ -0,0 +1,9 @@ +module ActiveRecord + module Type + class DecimalWithoutScale < ActiveModel::Type::BigInteger # :nodoc: + def type + :decimal + end + end + end +end 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/type/text.rb b/activerecord/lib/active_record/type/text.rb new file mode 100644 index 0000000000..cb1949700a --- /dev/null +++ b/activerecord/lib/active_record/type/text.rb @@ -0,0 +1,9 @@ +module ActiveRecord + module Type + class Text < ActiveModel::Type::String # :nodoc: + def type + :text + end + end + end +end diff --git a/activemodel/lib/active_model/type/unsigned_integer.rb b/activerecord/lib/active_record/type/unsigned_integer.rb index 288fa23efe..9ae0109f9f 100644 --- a/activemodel/lib/active_model/type/unsigned_integer.rb +++ b/activerecord/lib/active_record/type/unsigned_integer.rb @@ -1,6 +1,6 @@ -module ActiveModel +module ActiveRecord module Type - class UnsignedInteger < Integer # :nodoc: + class UnsignedInteger < ActiveModel::Type::Integer # :nodoc: private def max_value diff --git a/activerecord/lib/active_record/type_caster/connection.rb b/activerecord/lib/active_record/type_caster/connection.rb index 6c54792e26..9f7bbe8843 100644 --- a/activerecord/lib/active_record/type_caster/connection.rb +++ b/activerecord/lib/active_record/type_caster/connection.rb @@ -12,6 +12,8 @@ module ActiveRecord connection.type_cast_from_column(column, value) 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 :table_name diff --git a/activerecord/lib/active_record/type_caster/map.rb b/activerecord/lib/active_record/type_caster/map.rb index 52529a6b42..9f79723125 100644 --- a/activerecord/lib/active_record/type_caster/map.rb +++ b/activerecord/lib/active_record/type_caster/map.rb @@ -11,6 +11,8 @@ module ActiveRecord type.serialize(value) 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 :types diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index c013a4518f..9633f226f0 100644 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -68,7 +68,7 @@ module ActiveRecord alias_method :validate, :valid? - protected + private def default_validation_context new_record? ? :create : :update @@ -78,7 +78,7 @@ module ActiveRecord raise(RecordInvalid.new(self)) end - def perform_validations(options = {}) # :nodoc: + def perform_validations(options = {}) options[:validate] == false || valid?(options[:context]) end end diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index 512fdadacc..154cf5f1a4 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -33,13 +33,13 @@ module ActiveRecord end end - protected + private # The check for an existing value should be run from a class that # isn't abstract. This means working down from the current class # (self), to the first non-abstract class. Since classes don't know # their subclasses, we have to build the hierarchy between self and # the record's class. - def find_finder_class_for(record) #:nodoc: + def find_finder_class_for(record) class_hierarchy = [record.class] while class_hierarchy.first != @klass @@ -49,38 +49,8 @@ module ActiveRecord class_hierarchy.detect { |klass| !klass.abstract_class? } end - def build_relation(klass, attribute, value) # :nodoc: - if reflection = klass._reflect_on_association(attribute) - attribute = reflection.foreign_key - value = value.attributes[reflection.klass.primary_key] unless value.nil? - end - - if value.nil? - return klass.unscoped.where!(attribute => value) - end - - # the attribute may be an aliased attribute - if klass.attribute_alias?(attribute) - attribute = klass.attribute_alias(attribute) - end - - attribute_name = attribute.to_s - - table = klass.arel_table - column = klass.columns_hash[attribute_name] - cast_type = klass.type_for_attribute(attribute_name) - - comparison = if !options[:case_sensitive] - # will use SQL LOWER function before comparison, unless it detects a case insensitive collation - klass.connection.case_insensitive_comparison(table, attribute, column, value) - else - klass.connection.case_sensitive_comparison(table, attribute, column, value) - end - klass.unscoped.tap do |scope| - parts = [comparison] - binds = [Relation::QueryAttribute.new(attribute_name, value, cast_type)] - scope.where_clause += Relation::WhereClause.new(parts, binds) - end + def build_relation(klass, attribute, value) + klass.unscoped.where!({ attribute => value }, options) end def scope_relation(record, relation) 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 12d1f58f67..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,12 +10,16 @@ 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. + # Workaround for Ruby 2.2 "private attribute?" warning. protected attr_reader :migration_action, :join_tables + private + # 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. @@ -52,7 +56,6 @@ module ActiveRecord end.to_sym end - private def attributes_with_index attributes.select { |a| !a.reference? && a.has_index? } end 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 f1ddc61688..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 @@ -33,7 +33,7 @@ module ActiveRecord hook_for :test_framework - protected + private def attributes_with_index attributes.select { |a| !a.reference? && a.has_index? } @@ -41,7 +41,7 @@ module ActiveRecord # FIXME: Change this file to a symlink once RubyGems 2.5.0 is required. def generate_application_record - if self.behavior == :invoke && !application_record_exist? + if behavior == :invoke && !application_record_exist? template "application_record.rb", application_record_file_name end end diff --git a/activerecord/test/assets/schema_dump_5_1.yml b/activerecord/test/assets/schema_dump_5_1.yml new file mode 100644 index 0000000000..f37977daf2 --- /dev/null +++ b/activerecord/test/assets/schema_dump_5_1.yml @@ -0,0 +1,345 @@ +--- !ruby/object:ActiveRecord::ConnectionAdapters::SchemaCache +columns: + posts: + - &1 !ruby/object:ActiveRecord::ConnectionAdapters::Column + name: id + table_name: posts + sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata + sql_type: INTEGER + type: :integer + limit: + precision: + scale: + 'null': false + default: + default_function: + collation: + comment: + - &2 !ruby/object:ActiveRecord::ConnectionAdapters::Column + name: author_id + table_name: posts + sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata + sql_type: integer + type: :integer + limit: + precision: + scale: + 'null': true + default: + default_function: + collation: + comment: + - &3 !ruby/object:ActiveRecord::ConnectionAdapters::Column + name: title + table_name: posts + sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata + sql_type: varchar + type: :string + limit: + precision: + scale: + 'null': false + default: + default_function: + collation: + comment: + - &4 !ruby/object:ActiveRecord::ConnectionAdapters::Column + name: body + table_name: posts + sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata + sql_type: text + type: :text + limit: + precision: + scale: + 'null': false + default: + default_function: + collation: + comment: + - &5 !ruby/object:ActiveRecord::ConnectionAdapters::Column + name: type + table_name: posts + sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata + sql_type: varchar + type: :string + limit: + precision: + scale: + 'null': true + default: + default_function: + collation: + comment: + - &6 !ruby/object:ActiveRecord::ConnectionAdapters::Column + name: comments_count + table_name: posts + sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata + sql_type: integer + type: :integer + limit: + precision: + scale: + 'null': true + default: '0' + default_function: + collation: + comment: + - &7 !ruby/object:ActiveRecord::ConnectionAdapters::Column + name: taggings_with_delete_all_count + table_name: posts + sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata + sql_type: integer + type: :integer + limit: + precision: + scale: + 'null': true + default: '0' + default_function: + collation: + comment: + - &8 !ruby/object:ActiveRecord::ConnectionAdapters::Column + name: taggings_with_destroy_count + table_name: posts + sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata + sql_type: integer + type: :integer + limit: + precision: + scale: + 'null': true + default: '0' + default_function: + collation: + comment: + - &9 !ruby/object:ActiveRecord::ConnectionAdapters::Column + name: tags_count + table_name: posts + sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata + sql_type: integer + type: :integer + limit: + precision: + scale: + 'null': true + default: '0' + default_function: + collation: + comment: + - &10 !ruby/object:ActiveRecord::ConnectionAdapters::Column + name: tags_with_destroy_count + table_name: posts + sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata + sql_type: integer + type: :integer + limit: + precision: + scale: + 'null': true + default: '0' + default_function: + collation: + comment: + - &11 !ruby/object:ActiveRecord::ConnectionAdapters::Column + name: tags_with_nullify_count + table_name: posts + sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata + sql_type: integer + type: :integer + limit: + precision: + scale: + 'null': true + default: '0' + default_function: + collation: + comment: +columns_hash: + posts: + id: *1 + author_id: *2 + title: *3 + body: *4 + type: *5 + comments_count: *6 + taggings_with_delete_all_count: *7 + taggings_with_destroy_count: *8 + tags_count: *9 + tags_with_destroy_count: *10 + tags_with_nullify_count: *11 +primary_keys: + posts: id +data_sources: + ar_internal_metadata: true + table_with_autoincrement: true + accounts: true + admin_accounts: true + admin_users: true + aircraft: true + articles: true + articles_magazines: true + articles_tags: true + audit_logs: true + authors: true + author_addresses: true + author_favorites: true + auto_id_tests: true + binaries: true + birds: true + books: true + booleans: true + bulbs: true + CamelCase: true + cars: true + carriers: true + categories: true + categories_posts: true + categorizations: true + citations: true + clubs: true + collections: true + colnametests: true + columns: true + comments: true + companies: true + content: true + content_positions: true + vegetables: true + computers: true + computers_developers: true + contracts: true + customers: true + customer_carriers: true + dashboards: true + developers: true + developers_projects: true + dog_lovers: true + dogs: true + doubloons: true + edges: true + engines: true + entrants: true + essays: true + events: true + eyes: true + funny_jokes: true + cold_jokes: true + friendships: true + goofy_string_id: true + having: true + guids: true + guitars: true + inept_wizards: true + integer_limits: true + invoices: true + iris: true + items: true + jobs: true + jobs_pool: true + keyboards: true + legacy_things: true + lessons: true + lessons_students: true + students: true + lint_models: true + line_items: true + lions: true + lock_without_defaults: true + lock_without_defaults_cust: true + magazines: true + mateys: true + members: true + member_details: true + member_friends: true + memberships: true + member_types: true + mentors: true + minivans: true + minimalistics: true + mixed_case_monkeys: true + mixins: true + movies: true + notifications: true + numeric_data: true + orders: true + organizations: true + owners: true + paint_colors: true + paint_textures: true + parrots: true + parrots_pirates: true + parrots_treasures: true + people: true + peoples_treasures: true + personal_legacy_things: true + pets: true + pets_treasures: true + pirates: true + posts: true + serialized_posts: true + images: true + price_estimates: true + products: true + product_types: true + projects: true + randomly_named_table1: true + randomly_named_table2: true + randomly_named_table3: true + ratings: true + readers: true + references: true + shape_expressions: true + ships: true + ship_parts: true + prisoners: true + shop_accounts: true + speedometers: true + sponsors: true + string_key_objects: true + subscribers: true + subscriptions: true + tags: true + taggings: true + tasks: true + topics: true + toys: true + traffic_lights: true + treasures: true + tuning_pegs: true + tyres: true + variants: true + vertices: true + warehouse-things: true + circles: true + squares: true + triangles: true + non_poly_ones: true + non_poly_twos: true + men: true + faces: true + interests: true + zines: true + wheels: true + countries: true + treaties: true + countries_treaties: true + liquid: true + molecules: true + electrons: true + weirds: true + nodes: true + trees: true + hotels: true + departments: true + cake_designers: true + drink_designers: true + chefs: true + recipes: true + records: true + overloaded_types: true + users: true + test_with_keyword_column_name: true + fk_test_has_pk: true + fk_test_has_fk: true +version: 0 diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index 8bcecf2ed3..0c9d1dff9d 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 @@ -182,35 +175,15 @@ module ActiveRecord assert_not_nil error.cause 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 + def test_not_null_violations_are_translated_to_specific_exception + error = assert_raises(ActiveRecord::NotNullViolation) do + Post.create 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 + assert_not_nil error.cause + end + unless current_adapter?(:SQLite3Adapter) def test_value_limit_violations_are_translated_to_specific_exception error = assert_raises(ActiveRecord::ValueTooLong) do Event.create(title: "abcdefgh") @@ -218,22 +191,13 @@ module ActiveRecord assert_not_nil error.cause 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" + def test_numeric_value_out_of_ranges_are_translated_to_specific_exception + error = assert_raises(ActiveRecord::RangeError) do + Book.connection.create("INSERT INTO books(author_id) VALUES (9223372036854775808)") end + + assert_not_nil error.cause end end @@ -269,25 +233,68 @@ module ActiveRecord unless current_adapter?(:PostgreSQLAdapter) def test_log_invalid_encoding - error = assert_raise ActiveRecord::StatementInvalid do + error = assert_raises RuntimeError do @connection.send :log, "SELECT 'ы' FROM DUAL" do raise "ы".force_encoding(Encoding::ASCII_8BIT) end end - assert_not_nil error.cause + assert_not_nil error.message end end + end + + 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 - if current_adapter?(:Mysql2Adapter, :SQLite3Adapter) - def test_tables_returning_both_tables_and_views_is_deprecated - assert_deprecated { @connection.tables } + 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_passing_arguments_to_tables_is_deprecated - assert_deprecated { @connection.tables(:books) } + 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 a70eb5a094..67e1efde27 100644 --- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb @@ -28,12 +28,15 @@ class Mysql2ActiveSchemaTest < ActiveRecord::Mysql2TestCase expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(15)) " assert_equal expected, add_index(:people, [:last_name, :first_name], length: 15) + assert_equal expected, add_index(:people, ["last_name", "first_name"], length: 15) expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`) " assert_equal expected, add_index(:people, [:last_name, :first_name], length: { last_name: 15 }) + assert_equal expected, add_index(:people, ["last_name", "first_name"], length: { last_name: 15 }) expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(10)) " assert_equal expected, add_index(:people, [:last_name, :first_name], length: { last_name: 15, first_name: 10 }) + assert_equal expected, add_index(:people, ["last_name", :first_name], length: { last_name: 15, "first_name" => 10 }) %w(SPATIAL FULLTEXT UNIQUE).each do |type| expected = "CREATE #{type} INDEX `index_people_on_last_name` ON `people` (`last_name`) " @@ -89,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 9c109d8a24..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] @@ -186,7 +187,7 @@ class Mysql2ConnectionTest < ActiveRecord::Mysql2TestCase "expected release_advisory_lock to return false when there was no lock to release" end - protected + private def test_lock_free(lock_name) @connection.select_value("SELECT IS_FREE_LOCK(#{@connection.quote(lock_name)})") == 1 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/mysql2_adapter_test.rb b/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb index 69336eb906..aab3dcb724 100644 --- a/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb +++ b/activerecord/test/cases/adapters/mysql2/mysql2_adapter_test.rb @@ -65,6 +65,19 @@ class Mysql2AdapterTest < ActiveRecord::Mysql2TestCase @conn.columns_for_distinct("posts.id", [order]) end + def test_errors_for_bigint_fks_on_integer_pk_table + # table old_cars has primary key of integer + + error = assert_raises(ActiveRecord::MismatchedForeignKey) do + @conn.add_reference :engines, :old_car + @conn.add_foreign_key :engines, :old_cars + end + + assert_match "Column `old_car_id` on table `engines` has a type of `bigint(20)`", error.message + assert_not_nil error.cause + @conn.exec_query("ALTER TABLE engines DROP COLUMN old_car_id") + end + private def with_example_table(definition = "id int auto_increment primary key, number int, data varchar(255)", &block) diff --git a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb index b400f4d2f9..2c778b1150 100644 --- a/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb +++ b/activerecord/test/cases/adapters/mysql2/reserved_word_test.rb @@ -143,7 +143,7 @@ class Mysql2ReservedWordTest < ActiveRecord::Mysql2TestCase end # custom create table, uses execute on connection to create a table, note: escapes table_name, does NOT escape columns - def create_tables_directly (tables, connection = @connection) + def create_tables_directly(tables, connection = @connection) tables.each do |table_name, column_properties| connection.execute("CREATE TABLE `#{table_name}` ( #{column_properties} )") 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/sql_types_test.rb b/activerecord/test/cases/adapters/mysql2/sql_types_test.rb index bee42d48f1..d6e7f29a5c 100644 --- a/activerecord/test/cases/adapters/mysql2/sql_types_test.rb +++ b/activerecord/test/cases/adapters/mysql2/sql_types_test.rb @@ -8,7 +8,7 @@ class Mysql2SqlTypesTest < ActiveRecord::Mysql2TestCase assert_equal "blob", type_to_sql(:binary) end - def type_to_sql(*args) - ActiveRecord::Base.connection.type_to_sql(*args) + def type_to_sql(type, limit = nil) + ActiveRecord::Base.connection.type_to_sql(type, limit: limit) end end diff --git a/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb b/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb index 59475ad177..a0823be143 100644 --- a/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb +++ b/activerecord/test/cases/adapters/mysql2/unsigned_type_test.rb @@ -15,6 +15,7 @@ class Mysql2UnsignedTypeTest < ActiveRecord::Mysql2TestCase t.bigint :unsigned_bigint, unsigned: true t.float :unsigned_float, unsigned: true t.decimal :unsigned_decimal, unsigned: true, precision: 10, scale: 2 + t.column :unsigned_zerofill, "int unsigned zerofill" end end @@ -34,10 +35,10 @@ class Mysql2UnsignedTypeTest < ActiveRecord::Mysql2TestCase assert_raise(ActiveModel::RangeError) do UnsignedType.create(unsigned_bigint: -10) end - assert_raise(ActiveRecord::StatementInvalid) do + assert_raise(ActiveRecord::RangeError) do UnsignedType.create(unsigned_float: -10.0) end - assert_raise(ActiveRecord::StatementInvalid) do + assert_raise(ActiveRecord::RangeError) do UnsignedType.create(unsigned_decimal: -10.0) end end @@ -48,7 +49,6 @@ class Mysql2UnsignedTypeTest < ActiveRecord::Mysql2TestCase t.unsigned_bigint :unsigned_bigint_t t.unsigned_float :unsigned_float_t t.unsigned_decimal :unsigned_decimal_t, precision: 10, scale: 2 - t.column :unsigned_zerofill, "int unsigned zerofill" end @connection.columns("unsigned_types").select { |c| /^unsigned_/.match?(c.name) }.each do |column| 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/active_schema_test.rb b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb index d3c65f3d94..b787de8453 100644 --- a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb @@ -39,6 +39,10 @@ class PostgresqlActiveSchemaTest < ActiveRecord::PostgreSQLTestCase expected = %(CREATE INDEX CONCURRENTLY "index_people_on_last_name" ON "people" ("last_name")) assert_equal expected, add_index(:people, :last_name, algorithm: :concurrently) + expected = %(CREATE INDEX "index_people_on_last_name_and_first_name" ON "people" ("last_name" DESC, "first_name" ASC)) + assert_equal expected, add_index(:people, [:last_name, :first_name], order: { last_name: :desc, first_name: :asc }) + assert_equal expected, add_index(:people, ["last_name", :first_name], order: { last_name: :desc, "first_name" => :asc }) + %w(gin gist hash btree).each do |type| expected = %(CREATE INDEX "index_people_on_last_name" ON "people" USING #{type} ("last_name")) assert_equal expected, add_index(:people, :last_name, using: type) 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..505c297cd4 100644 --- a/activerecord/test/cases/adapters/postgresql/bytea_test.rb +++ b/activerecord/test/cases/adapters/postgresql/bytea_test.rb @@ -32,9 +32,9 @@ class PostgresqlByteaTest < ActiveRecord::PostgreSQLTestCase end def test_binary_columns_are_limitless_the_upper_limit_is_one_GB - assert_equal "bytea", @connection.type_to_sql(:binary, 100_000) + assert_equal "bytea", @connection.type_to_sql(:binary, limit: 100_000) assert_raise ActiveRecord::ActiveRecordError do - @connection.type_to_sql :binary, 4294967295 + @connection.type_to_sql(:binary, limit: 4294967295) end end @@ -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 48c82cb7b9..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. @@ -245,7 +245,7 @@ module ActiveRecord end end - protected + private def with_warning_suppression log_level = @connection.client_min_messages diff --git a/activerecord/test/cases/adapters/postgresql/datatype_test.rb b/activerecord/test/cases/adapters/postgresql/datatype_test.rb index 0ac8b7339b..0725fde5ae 100644 --- a/activerecord/test/cases/adapters/postgresql/datatype_test.rb +++ b/activerecord/test/cases/adapters/postgresql/datatype_test.rb @@ -28,12 +28,12 @@ class PostgresqlDataTypeTest < ActiveRecord::PostgreSQLTestCase end def test_data_type_of_time_types - assert_equal :string, @first_time.column_for_attribute(:time_interval).type - assert_equal :string, @first_time.column_for_attribute(:scaled_time_interval).type + assert_equal :interval, @first_time.column_for_attribute(:time_interval).type + assert_equal :interval, @first_time.column_for_attribute(:scaled_time_interval).type end def test_data_type_of_oid_types - assert_equal :integer, @first_oid.column_for_attribute(:obj_id).type + assert_equal :oid, @first_oid.column_for_attribute(:obj_id).type end def test_time_values @@ -61,9 +61,9 @@ class PostgresqlDataTypeTest < ActiveRecord::PostgreSQLTestCase end def test_text_columns_are_limitless_the_upper_limit_is_one_GB - assert_equal "text", @connection.type_to_sql(:text, 100_000) + assert_equal "text", @connection.type_to_sql(:text, limit: 100_000) assert_raise ActiveRecord::ActiveRecordError do - @connection.type_to_sql :text, 4294967295 + @connection.type_to_sql(:text, limit: 4294967295) end end end diff --git a/activerecord/test/cases/adapters/postgresql/geometric_test.rb b/activerecord/test/cases/adapters/postgresql/geometric_test.rb index a65d4d1ad9..c1f3a4ae2c 100644 --- a/activerecord/test/cases/adapters/postgresql/geometric_test.rb +++ b/activerecord/test/cases/adapters/postgresql/geometric_test.rb @@ -96,7 +96,7 @@ class PostgresqlPointTest < ActiveRecord::PostgreSQLTestCase assert_nothing_raised { PostgresqlPoint.new(x: "") } p = PostgresqlPoint.new(x: "") - assert_equal nil, p.x + assert_nil p.x end def test_array_of_points_round_trip 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/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/quoting_test.rb b/activerecord/test/cases/adapters/postgresql/quoting_test.rb index a9e81ab3d8..141baffa5b 100644 --- a/activerecord/test/cases/adapters/postgresql/quoting_test.rb +++ b/activerecord/test/cases/adapters/postgresql/quoting_test.rb @@ -36,7 +36,7 @@ module ActiveRecord def test_quote_bit_string value = "'); SELECT * FROM users; /*\n01\n*/--" type = OID::Bit.new - assert_equal nil, @conn.quote(type.serialize(value)) + assert_nil @conn.quote(type.serialize(value)) end end end diff --git a/activerecord/test/cases/adapters/postgresql/schema_test.rb b/activerecord/test/cases/adapters/postgresql/schema_test.rb index 237e9ff6a5..8756507531 100644 --- a/activerecord/test/cases/adapters/postgresql/schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/schema_test.rb @@ -301,13 +301,13 @@ class SchemaTest < ActiveRecord::PostgreSQLTestCase def test_index_name_exists with_schema_search_path(SCHEMA_NAME) do - assert @connection.index_name_exists?(TABLE_NAME, INDEX_A_NAME, true) - assert @connection.index_name_exists?(TABLE_NAME, INDEX_B_NAME, true) - assert @connection.index_name_exists?(TABLE_NAME, INDEX_C_NAME, true) - assert @connection.index_name_exists?(TABLE_NAME, INDEX_D_NAME, true) - assert @connection.index_name_exists?(TABLE_NAME, INDEX_E_NAME, true) - assert @connection.index_name_exists?(TABLE_NAME, INDEX_E_NAME, true) - assert_not @connection.index_name_exists?(TABLE_NAME, "missing_index", true) + assert @connection.index_name_exists?(TABLE_NAME, INDEX_A_NAME) + assert @connection.index_name_exists?(TABLE_NAME, INDEX_B_NAME) + assert @connection.index_name_exists?(TABLE_NAME, INDEX_C_NAME) + assert @connection.index_name_exists?(TABLE_NAME, INDEX_D_NAME) + assert @connection.index_name_exists?(TABLE_NAME, INDEX_E_NAME) + assert @connection.index_name_exists?(TABLE_NAME, INDEX_E_NAME) + assert_not @connection.index_name_exists?(TABLE_NAME, "missing_index") end end diff --git a/activerecord/test/cases/adapters/postgresql/transaction_test.rb b/activerecord/test/cases/adapters/postgresql/transaction_test.rb index f130e344c4..9b42d0383d 100644 --- a/activerecord/test/cases/adapters/postgresql/transaction_test.rb +++ b/activerecord/test/cases/adapters/postgresql/transaction_test.rb @@ -89,7 +89,7 @@ module ActiveRecord end end - protected + private def with_warning_suppression log_level = ActiveRecord::Base.connection.client_min_messages 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/utils_test.rb b/activerecord/test/cases/adapters/postgresql/utils_test.rb index 7f6ea3887d..9f9e3bda2f 100644 --- a/activerecord/test/cases/adapters/postgresql/utils_test.rb +++ b/activerecord/test/cases/adapters/postgresql/utils_test.rb @@ -56,7 +56,7 @@ class PostgreSQLNameTest < ActiveRecord::PostgreSQLTestCase test "can be used as hash key" do hash = { Name.new("schema", "article_seq") => "success" } assert_equal "success", hash[Name.new("schema", "article_seq")] - assert_equal nil, hash[Name.new("schema", "articles")] - assert_equal nil, hash[Name.new("public", "article_seq")] + assert_nil hash[Name.new("schema", "articles")] + assert_nil hash[Name.new("public", "article_seq")] end end diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb index 4604c2eb3b..6aa6a79705 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 @@ -210,7 +210,7 @@ class PostgresqlUUIDGenerationTest < ActiveRecord::PostgreSQLTestCase def test_pk_and_sequence_for_uuid_primary_key pk, seq = connection.pk_and_sequence_for("pg_uuids") assert_equal "id", pk - assert_equal nil, seq + assert_nil seq end def test_schema_dumper_for_uuid_primary_key @@ -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 @@ -330,7 +347,6 @@ class PostgresqlUUIDTestInverseOf < ActiveRecord::PostgreSQLTestCase assert_raise ActiveRecord::RecordNotFound do UuidPost.find(123456) end - end def test_find_by_with_uuid diff --git a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb index 8342b05870..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 @@ -75,7 +76,7 @@ class CopyTableTest < ActiveRecord::SQLite3TestCase test_copy_table "binaries", "binaries2" end -protected +private def copy_table(from, to, options = {}) @connection.copy_table(from, to, { temporary: true }.merge(options)) end diff --git a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb index 80a37e83ff..9750840051 100644 --- a/activerecord/test/cases/adapters/sqlite3/quoting_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/quoting_test.rb @@ -37,7 +37,7 @@ class SQLite3QuotingTest < ActiveRecord::SQLite3TestCase end def test_type_cast_nil - assert_equal nil, @conn.type_cast(nil) + assert_nil @conn.type_cast(nil) end def test_type_cast_true 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/adapters/sqlite3/statement_pool_test.rb b/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb index aebcce3691..37ff973397 100644 --- a/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/statement_pool_test.rb @@ -3,7 +3,6 @@ require "cases/helper" class SQLite3StatementPoolTest < ActiveRecord::SQLite3TestCase if Process.respond_to?(:fork) def test_cache_is_per_pid - cache = ActiveRecord::ConnectionAdapters::SQLite3Adapter::StatementPool.new(10) cache["foo"] = "bar" assert_equal "bar", cache["foo"] diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index 72f1b3b125..5875a1871f 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -1057,7 +1057,7 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase comment.parent = nil comment.save! - assert_equal nil, comment.reload.parent + assert_nil comment.reload.parent assert_equal 0, comments(:greetings).reload.children_count end @@ -1072,6 +1072,20 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert_equal 1, parent.reload.children_count end + def test_belongs_to_with_out_of_range_value_assigning + model = Class.new(Comment) do + def self.name; "Temp"; end + validates :post, presence: true + end + + comment = model.new + comment.post_id = 9223372036854775808 # out of range in the bigint + + assert_nil comment.post + assert_not comment.valid? + assert_equal [{ error: :blank }], comment.errors.details[:post] + end + def test_polymorphic_with_custom_primary_key toy = Toy.create! sponsor = Sponsor.create!(sponsorable: toy) @@ -1117,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_load_nested_include_test.rb b/activerecord/test/cases/associations/eager_load_nested_include_test.rb index ed1b0f5226..e9f551b6b2 100644 --- a/activerecord/test/cases/associations/eager_load_nested_include_test.rb +++ b/activerecord/test/cases/associations/eager_load_nested_include_test.rb @@ -12,7 +12,7 @@ module Remembered included do after_create :remember - protected + private def remember; self.class.remembered << self; end end diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index dc04ccdccc..11f4aae5b3 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -241,7 +241,7 @@ class EagerAssociationTest < ActiveRecord::TestCase post = assert_queries(1) { Post.all.merge!(includes: { author_with_address: :author_address }).find(post.id) } # find the post, then find the author which is null so no query for the author or address assert_no_queries do - assert_equal nil, post.author_with_address + assert_nil post.author_with_address end end @@ -250,7 +250,7 @@ class EagerAssociationTest < ActiveRecord::TestCase sponsor.update!(sponsorable: nil) sponsor = assert_queries(1) { Sponsor.all.merge!(includes: :sponsorable).find(sponsor.id) } assert_no_queries do - assert_equal nil, sponsor.sponsorable + assert_nil sponsor.sponsorable end end @@ -261,7 +261,7 @@ class EagerAssociationTest < ActiveRecord::TestCase assert_nothing_raised { Sponsor.all.merge!(includes: :sponsorable).find(sponsor.id) } end assert_no_queries do - assert_equal nil, sponsor.sponsorable + assert_nil sponsor.sponsorable end end @@ -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 d657be71cc..cbecfa84ff 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -187,7 +187,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase ship.parts.clear part.reload - assert_equal nil, part.ship + assert_nil part.ship assert !part.updated_at_changed? end @@ -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 8defb09db7..ea52fb5a67 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, @@ -402,7 +405,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase end end - assert_equal nil, reference.reload.job_id + assert_nil reference.reload.job_id ensure Reference.make_comments = false end @@ -423,7 +426,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase end # Check that the destroy callback on Reference did not run - assert_equal nil, person.reload.comments + assert_nil person.reload.comments ensure Reference.make_comments = false end @@ -485,7 +488,7 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase end references.each do |reference| - assert_equal nil, reference.reload.job_id + assert_nil reference.reload.job_id end end @@ -880,7 +883,6 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase book.subscriber_ids = [] assert_equal [], book.subscribers.reload end - end def test_collection_singular_ids_setter_with_changed_primary_key @@ -904,6 +906,13 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase assert_match(/Couldn't find all Clients with 'name'/, e.message) end + def test_collection_singular_ids_through_setter_raises_exception_when_invalid_ids_set + author = authors(:david) + ids = [categories(:general).name, "Unknown"] + e = assert_raises(ActiveRecord::RecordNotFound) { author.essay_category_ids = ids } + assert_equal "Couldn't find all Categories with 'name': (General, Unknown) (found 1 results, but was looking for 2)", e.message + end + def test_build_a_model_from_hm_through_association_with_where_clause assert_nothing_raised { books(:awdr).subscribers.where(nick: "marklazz").build } end @@ -1197,12 +1206,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! @@ -1230,4 +1233,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 6ba062a248..38a729d2d4 100644 --- a/activerecord/test/cases/associations/has_one_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_through_associations_test.rb @@ -82,10 +82,17 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase def test_set_record_to_nil_should_delete_association @member.club = nil @member.reload - assert_equal nil, @member.current_membership + assert_nil @member.current_membership 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 @@ -110,12 +117,12 @@ class HasOneThroughAssociationsTest < ActiveRecord::TestCase # conditions on the through table assert_equal clubs(:moustache_club), Member.all.merge!(includes: :favourite_club).find(@member.id).favourite_club memberships(:membership_of_favourite_club).update_columns(favourite: false) - assert_equal nil, Member.all.merge!(includes: :favourite_club).find(@member.id).reload.favourite_club + assert_nil Member.all.merge!(includes: :favourite_club).find(@member.id).reload.favourite_club # conditions on the source table assert_equal clubs(:moustache_club), Member.all.merge!(includes: :hairy_club).find(@member.id).hairy_club clubs(:moustache_club).update_columns(name: "Association of Clean-Shaven Persons") - assert_equal nil, Member.all.merge!(includes: :hairy_club).find(@member.id).reload.hairy_club + assert_nil Member.all.merge!(includes: :hairy_club).find(@member.id).reload.hairy_club end def test_has_one_through_polymorphic_with_source_type 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/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index ba6877a6a6..3dc0c0ce53 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -92,7 +92,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase test "attribute keys on a new instance" do t = Topic.new - assert_equal nil, t.title, "The topics table has a title column, so it should be nil" + assert_nil t.title, "The topics table has a title column, so it should be nil" assert_raise(NoMethodError) { t.title2 } end @@ -156,7 +156,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase keyboard = Keyboard.create keyboard.key_number = "10" assert_equal "10", keyboard.id_before_type_cast - assert_equal nil, keyboard.read_attribute_before_type_cast("id") + assert_nil keyboard.read_attribute_before_type_cast("id") assert_equal "10", keyboard.read_attribute_before_type_cast("key_number") assert_equal "10", keyboard.read_attribute_before_type_cast(:key_number) end @@ -213,7 +213,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase record.written_on = "345643456" assert_equal "345643456", record.written_on_before_type_cast - assert_equal nil, record.written_on + assert_nil record.written_on record.written_on = "2009-10-11 12:13:14" assert_equal "2009-10-11 12:13:14", record.written_on_before_type_cast @@ -319,6 +319,13 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_equal "Still another topic: part 4", topic.title end + test "write_attribute can write aliased attributes as well" do + topic = Topic.new(title: "Don't change the topic") + topic.write_attribute :heading, "New topic" + + assert_equal "New topic", topic.title + end + test "read_attribute" do topic = Topic.new topic.title = "Don't change the topic" @@ -329,6 +336,16 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_equal "Don't change the topic", topic[:title] end + test "read_attribute can read aliased attributes as well" do + topic = Topic.new(title: "Don't change the topic") + + assert_equal "Don't change the topic", topic.read_attribute("heading") + assert_equal "Don't change the topic", topic["heading"] + + assert_equal "Don't change the topic", topic.read_attribute(:heading) + assert_equal "Don't change the topic", topic[:heading] + end + test "read_attribute raises ActiveModel::MissingAttributeError when the attribute does not exist" do computer = Computer.select("id").first assert_raises(ActiveModel::MissingAttributeError) { computer[:developer] } @@ -737,7 +754,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase test "time zone-aware attributes do not recurse infinitely on invalid values" do in_time_zone "Pacific Time (US & Canada)" do record = @target.new(bonus_time: []) - assert_equal nil, record.bonus_time + assert_nil record.bonus_time end end diff --git a/activerecord/test/cases/attribute_set_test.rb b/activerecord/test/cases/attribute_set_test.rb index 059b5b2401..bd4b200735 100644 --- a/activerecord/test/cases/attribute_set_test.rb +++ b/activerecord/test/cases/attribute_set_test.rb @@ -25,7 +25,7 @@ module ActiveRecord attributes = builder.build_from_database(foo: "3.3") assert_equal "3.3", attributes[:foo].value_before_type_cast - assert_equal nil, attributes[:bar].value_before_type_cast + assert_nil attributes[:bar].value_before_type_cast assert_equal :bar, attributes[:bar].name end diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index a3f82ed49d..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 = "" } @@ -1699,3 +1707,27 @@ class TestAutosaveAssociationWithTouch < ActiveRecord::TestCase assert_nothing_raised { invoice.line_items.create(amount: 10) } end end + +class TestAutosaveAssociationOnAHasManyAssociationWithInverse < ActiveRecord::TestCase + class Post < ActiveRecord::Base + has_many :comments, inverse_of: :post + end + + class Comment < ActiveRecord::Base + belongs_to :post, inverse_of: :comments + + attr_accessor :post_comments_count + after_save do + self.post_comments_count = post.comments.count + end + end + + def test_after_save_callback_with_autosave + post = Post.new(title: "Test", body: "...") + comment = post.comments.build(body: "...") + post.save! + + assert_equal 1, post.comments.count + assert_equal 1, comment.post_comments_count + end +end diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 4e9d78de5d..979a59f566 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,12 +98,11 @@ 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 + def test_primary_key_and_references_columns_should_be_identical_type + pk = Author.columns_hash["id"] + ref = Post.columns_hash["author_id"] + + assert_equal pk.bigint?, ref.bigint? end def test_many_mutations @@ -144,10 +134,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 @@ -622,7 +610,7 @@ class BasicsTest < ActiveRecord::TestCase Topic.find(topic.id).destroy end - assert_equal nil, Topic.find_by_id(topic.id) + assert_nil Topic.find_by_id(topic.id) end def test_comparison_with_different_objects @@ -1379,12 +1367,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..1813534b62 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,14 @@ 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 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 +421,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..b3c86586d0 100644 --- a/activerecord/test/cases/callbacks_test.rb +++ b/activerecord/test/cases/callbacks_test.rb @@ -6,10 +6,6 @@ class CallbackDeveloper < ActiveRecord::Base self.table_name = "developers" class << self - def callback_string(callback_method) - "history << [#{callback_method.to_sym.inspect}, :string]" - end - def callback_proc(callback_method) Proc.new { |model| model.history << [callback_method, :proc] } end @@ -33,7 +29,6 @@ class CallbackDeveloper < ActiveRecord::Base ActiveRecord::Callbacks::CALLBACKS.each do |callback_method| next if callback_method.to_s.start_with?("around_") define_callback_method(callback_method) - ActiveSupport::Deprecation.silence { send(callback_method, callback_string(callback_method)) } send(callback_method, callback_proc(callback_method)) send(callback_method, callback_object(callback_method)) send(callback_method) { |model| model.history << [callback_method, :block] } @@ -44,11 +39,6 @@ class CallbackDeveloper < ActiveRecord::Base end end -class CallbackDeveloperWithFalseValidation < CallbackDeveloper - before_validation proc { |model| model.history << [:before_validation, :returning_false]; false } - before_validation proc { |model| model.history << [:before_validation, :should_never_get_here] } -end - class CallbackDeveloperWithHaltedValidation < CallbackDeveloper before_validation proc { |model| model.history << [:before_validation, :throwing_abort]; throw(:abort) } before_validation proc { |model| model.history << [:before_validation, :should_never_get_here] } @@ -125,11 +115,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 @@ -137,23 +127,6 @@ class ContextualCallbacksDeveloper < ActiveRecord::Base end end -class CallbackCancellationDeveloper < ActiveRecord::Base - self.table_name = "developers" - - attr_reader :after_save_called, :after_create_called, :after_update_called, :after_destroy_called - attr_accessor :cancel_before_save, :cancel_before_create, :cancel_before_update, :cancel_before_destroy - - before_save { defined?(@cancel_before_save) ? !@cancel_before_save : false } - before_create { !@cancel_before_create } - before_update { !@cancel_before_update } - before_destroy { !@cancel_before_destroy } - - after_save { @after_save_called = true } - after_update { @after_update_called = true } - after_create { @after_create_called = true } - after_destroy { @after_destroy_called = true } -end - class CallbackHaltedDeveloper < ActiveRecord::Base self.table_name = "developers" @@ -178,7 +151,6 @@ class CallbacksTest < ActiveRecord::TestCase david = CallbackDeveloper.new assert_equal [ [ :after_initialize, :method ], - [ :after_initialize, :string ], [ :after_initialize, :proc ], [ :after_initialize, :object ], [ :after_initialize, :block ], @@ -189,12 +161,10 @@ class CallbacksTest < ActiveRecord::TestCase david = CallbackDeveloper.find(1) assert_equal [ [ :after_find, :method ], - [ :after_find, :string ], [ :after_find, :proc ], [ :after_find, :object ], [ :after_find, :block ], [ :after_initialize, :method ], - [ :after_initialize, :string ], [ :after_initialize, :proc ], [ :after_initialize, :object ], [ :after_initialize, :block ], @@ -206,17 +176,14 @@ class CallbacksTest < ActiveRecord::TestCase david.valid? assert_equal [ [ :after_initialize, :method ], - [ :after_initialize, :string ], [ :after_initialize, :proc ], [ :after_initialize, :object ], [ :after_initialize, :block ], [ :before_validation, :method ], - [ :before_validation, :string ], [ :before_validation, :proc ], [ :before_validation, :object ], [ :before_validation, :block ], [ :after_validation, :method ], - [ :after_validation, :string ], [ :after_validation, :proc ], [ :after_validation, :object ], [ :after_validation, :block ], @@ -228,22 +195,18 @@ class CallbacksTest < ActiveRecord::TestCase david.valid? assert_equal [ [ :after_find, :method ], - [ :after_find, :string ], [ :after_find, :proc ], [ :after_find, :object ], [ :after_find, :block ], [ :after_initialize, :method ], - [ :after_initialize, :string ], [ :after_initialize, :proc ], [ :after_initialize, :object ], [ :after_initialize, :block ], [ :before_validation, :method ], - [ :before_validation, :string ], [ :before_validation, :proc ], [ :before_validation, :object ], [ :before_validation, :block ], [ :after_validation, :method ], - [ :after_validation, :string ], [ :after_validation, :proc ], [ :after_validation, :object ], [ :after_validation, :block ], @@ -254,44 +217,36 @@ class CallbacksTest < ActiveRecord::TestCase david = CallbackDeveloper.create("name" => "David", "salary" => 1000000) assert_equal [ [ :after_initialize, :method ], - [ :after_initialize, :string ], [ :after_initialize, :proc ], [ :after_initialize, :object ], [ :after_initialize, :block ], [ :before_validation, :method ], - [ :before_validation, :string ], [ :before_validation, :proc ], [ :before_validation, :object ], [ :before_validation, :block ], [ :after_validation, :method ], - [ :after_validation, :string ], [ :after_validation, :proc ], [ :after_validation, :object ], [ :after_validation, :block ], [ :before_save, :method ], - [ :before_save, :string ], [ :before_save, :proc ], [ :before_save, :object ], [ :before_save, :block ], [ :before_create, :method ], - [ :before_create, :string ], [ :before_create, :proc ], [ :before_create, :object ], [ :before_create, :block ], [ :after_create, :method ], - [ :after_create, :string ], [ :after_create, :proc ], [ :after_create, :object ], [ :after_create, :block ], [ :after_save, :method ], - [ :after_save, :string ], [ :after_save, :proc ], [ :after_save, :object ], [ :after_save, :block ], [ :after_commit, :block ], [ :after_commit, :object ], [ :after_commit, :proc ], - [ :after_commit, :string ], [ :after_commit, :method ] ], david.history end @@ -323,49 +278,40 @@ class CallbacksTest < ActiveRecord::TestCase david.save assert_equal [ [ :after_find, :method ], - [ :after_find, :string ], [ :after_find, :proc ], [ :after_find, :object ], [ :after_find, :block ], [ :after_initialize, :method ], - [ :after_initialize, :string ], [ :after_initialize, :proc ], [ :after_initialize, :object ], [ :after_initialize, :block ], [ :before_validation, :method ], - [ :before_validation, :string ], [ :before_validation, :proc ], [ :before_validation, :object ], [ :before_validation, :block ], [ :after_validation, :method ], - [ :after_validation, :string ], [ :after_validation, :proc ], [ :after_validation, :object ], [ :after_validation, :block ], [ :before_save, :method ], - [ :before_save, :string ], [ :before_save, :proc ], [ :before_save, :object ], [ :before_save, :block ], [ :before_update, :method ], - [ :before_update, :string ], [ :before_update, :proc ], [ :before_update, :object ], [ :before_update, :block ], [ :after_update, :method ], - [ :after_update, :string ], [ :after_update, :proc ], [ :after_update, :object ], [ :after_update, :block ], [ :after_save, :method ], - [ :after_save, :string ], [ :after_save, :proc ], [ :after_save, :object ], [ :after_save, :block ], [ :after_commit, :block ], [ :after_commit, :object ], [ :after_commit, :proc ], - [ :after_commit, :string ], [ :after_commit, :method ] ], david.history end @@ -399,29 +345,24 @@ class CallbacksTest < ActiveRecord::TestCase david.destroy assert_equal [ [ :after_find, :method ], - [ :after_find, :string ], [ :after_find, :proc ], [ :after_find, :object ], [ :after_find, :block ], [ :after_initialize, :method ], - [ :after_initialize, :string ], [ :after_initialize, :proc ], [ :after_initialize, :object ], [ :after_initialize, :block ], [ :before_destroy, :method ], - [ :before_destroy, :string ], [ :before_destroy, :proc ], [ :before_destroy, :object ], [ :before_destroy, :block ], [ :after_destroy, :method ], - [ :after_destroy, :string ], [ :after_destroy, :proc ], [ :after_destroy, :object ], [ :after_destroy, :block ], [ :after_commit, :block ], [ :after_commit, :object ], [ :after_commit, :proc ], - [ :after_commit, :string ], [ :after_commit, :method ] ], david.history end @@ -431,82 +372,16 @@ class CallbacksTest < ActiveRecord::TestCase CallbackDeveloper.delete(david.id) assert_equal [ [ :after_find, :method ], - [ :after_find, :string ], [ :after_find, :proc ], [ :after_find, :object ], [ :after_find, :block ], [ :after_initialize, :method ], - [ :after_initialize, :string ], [ :after_initialize, :proc ], [ :after_initialize, :object ], [ :after_initialize, :block ], ], david.history end - def test_deprecated_before_save_returning_false - david = ImmutableDeveloper.find(1) - assert_deprecated do - assert david.valid? - assert !david.save - exc = assert_raise(ActiveRecord::RecordNotSaved) { david.save! } - assert_equal exc.record, david - assert_equal "Failed to save the record", exc.message - end - - david = ImmutableDeveloper.find(1) - david.salary = 10_000_000 - assert !david.valid? - assert !david.save - assert_raise(ActiveRecord::RecordInvalid) { david.save! } - - someone = CallbackCancellationDeveloper.find(1) - someone.cancel_before_save = true - assert_deprecated do - assert someone.valid? - assert !someone.save - end - assert_save_callbacks_not_called(someone) - end - - def test_deprecated_before_create_returning_false - someone = CallbackCancellationDeveloper.new - someone.cancel_before_create = true - assert_deprecated do - assert someone.valid? - assert !someone.save - end - assert_save_callbacks_not_called(someone) - end - - def test_deprecated_before_update_returning_false - someone = CallbackCancellationDeveloper.find(1) - someone.cancel_before_update = true - assert_deprecated do - assert someone.valid? - assert !someone.save - end - assert_save_callbacks_not_called(someone) - end - - def test_deprecated_before_destroy_returning_false - david = ImmutableDeveloper.find(1) - assert_deprecated do - assert !david.destroy - exc = assert_raise(ActiveRecord::RecordNotDestroyed) { david.destroy! } - assert_equal exc.record, david - assert_equal "Failed to destroy the record", exc.message - end - assert_not_nil ImmutableDeveloper.find_by_id(1) - - someone = CallbackCancellationDeveloper.find(1) - someone.cancel_before_destroy = true - assert_deprecated do - assert !someone.destroy - assert_raise(ActiveRecord::RecordNotDestroyed) { someone.destroy! } - end - assert !someone.after_destroy_called - end - def assert_save_callbacks_not_called(someone) assert !someone.after_save_called assert !someone.after_create_called @@ -527,7 +402,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 +429,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) @@ -564,50 +439,19 @@ class CallbacksTest < ActiveRecord::TestCase assert !someone.after_destroy_called end - def test_callback_returning_false - david = CallbackDeveloperWithFalseValidation.find(1) - assert_deprecated { david.save } - assert_equal [ - [ :after_find, :method ], - [ :after_find, :string ], - [ :after_find, :proc ], - [ :after_find, :object ], - [ :after_find, :block ], - [ :after_initialize, :method ], - [ :after_initialize, :string ], - [ :after_initialize, :proc ], - [ :after_initialize, :object ], - [ :after_initialize, :block ], - [ :before_validation, :method ], - [ :before_validation, :string ], - [ :before_validation, :proc ], - [ :before_validation, :object ], - [ :before_validation, :block ], - [ :before_validation, :returning_false ], - [ :after_rollback, :block ], - [ :after_rollback, :object ], - [ :after_rollback, :proc ], - [ :after_rollback, :string ], - [ :after_rollback, :method ], - ], david.history - end - def test_callback_throwing_abort david = CallbackDeveloperWithHaltedValidation.find(1) david.save assert_equal [ [ :after_find, :method ], - [ :after_find, :string ], [ :after_find, :proc ], [ :after_find, :object ], [ :after_find, :block ], [ :after_initialize, :method ], - [ :after_initialize, :string ], [ :after_initialize, :proc ], [ :after_initialize, :object ], [ :after_initialize, :block ], [ :before_validation, :method ], - [ :before_validation, :string ], [ :before_validation, :proc ], [ :before_validation, :object ], [ :before_validation, :block ], @@ -615,7 +459,6 @@ class CallbacksTest < ActiveRecord::TestCase [ :after_rollback, :block ], [ :after_rollback, :object ], [ :after_rollback, :proc ], - [ :after_rollback, :string ], [ :after_rollback, :method ], ], david.history end 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..d230700119 100644 --- a/activerecord/test/cases/column_definition_test.rb +++ b/activerecord/test/cases/column_definition_test.rb @@ -14,71 +14,19 @@ module ActiveRecord # Avoid column definitions in create table statements like: # `title` varchar(255) DEFAULT NULL def test_should_not_include_default_clause_when_default_is_null - column = Column.new("title", nil, SqlTypeMetadata.new(limit: 20)) - column_def = ColumnDefinition.new( - column.name, "string", - column.limit, column.precision, column.scale, column.default, column.null) + column_def = ColumnDefinition.new("title", "string", limit: 20) assert_equal "title varchar(20)", @viz.accept(column_def) end def test_should_include_default_clause_when_default_is_present - column = Column.new("title", "Hello", SqlTypeMetadata.new(limit: 20)) - column_def = ColumnDefinition.new( - column.name, "string", - column.limit, column.precision, column.scale, column.default, column.null) + column_def = ColumnDefinition.new("title", "string", limit: 20, default: "Hello") assert_equal "title varchar(20) DEFAULT 'Hello'", @viz.accept(column_def) end def test_should_specify_not_null_if_null_option_is_false - type_metadata = SqlTypeMetadata.new(limit: 20) - column = Column.new("title", "Hello", type_metadata, false) - column_def = ColumnDefinition.new( - column.name, "string", - column.limit, column.precision, column.scale, column.default, column.null) + column_def = ColumnDefinition.new("title", "string", limit: 20, default: "Hello", null: false) 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/comment_test.rb b/activerecord/test/cases/comment_test.rb index 262ad319be..a625299e8d 100644 --- a/activerecord/test/cases/comment_test.rb +++ b/activerecord/test/cases/comment_test.rb @@ -2,7 +2,6 @@ require "cases/helper" require "support/schema_dumping_helper" if ActiveRecord::Base.connection.supports_comments? - class CommentTest < ActiveRecord::TestCase include SchemaDumpingHelper @@ -102,6 +101,7 @@ if ActiveRecord::Base.connection.supports_comments? # Do all the stuff from other tests @connection.add_column :commenteds, :rating, :integer, comment: "I am running out of imagination" @connection.change_column :commenteds, :content, :string, comment: "Whoa, content describes itself!" + @connection.change_column :commenteds, :content, :string @connection.change_column :commenteds, :obvious, :string, comment: nil @connection.add_index :commenteds, :obvious, name: "idx_obvious", comment: "We need to see obvious comments" @@ -135,5 +135,4 @@ if ActiveRecord::Base.connection.supports_comments? assert_no_match %r[t\.string\s+"absent_comment", comment:\n], output 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/merge_and_resolve_default_url_config_test.rb b/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb index a8955152c3..8faa67255d 100644 --- a/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb +++ b/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb @@ -142,13 +142,13 @@ module ActiveRecord "database" => "foo", "host" => "localhost" } assert_equal expected, actual["default_env"] - assert_equal nil, actual["production"] - assert_equal nil, actual["development"] - assert_equal nil, actual["test"] - assert_equal nil, actual[:default_env] - assert_equal nil, actual[:production] - assert_equal nil, actual[:development] - assert_equal nil, actual[:test] + assert_nil actual["production"] + assert_nil actual["development"] + assert_nil actual["test"] + assert_nil actual[:default_env] + assert_nil actual[:production] + assert_nil actual[:development] + assert_nil actual[:test] end def test_blank_with_database_url_with_rails_env @@ -162,15 +162,15 @@ module ActiveRecord "host" => "localhost" } assert_equal expected, actual["not_production"] - assert_equal nil, actual["production"] - assert_equal nil, actual["default_env"] - assert_equal nil, actual["development"] - assert_equal nil, actual["test"] - assert_equal nil, actual[:default_env] - assert_equal nil, actual[:not_production] - assert_equal nil, actual[:production] - assert_equal nil, actual[:development] - assert_equal nil, actual[:test] + assert_nil actual["production"] + assert_nil actual["default_env"] + assert_nil actual["development"] + assert_nil actual["test"] + assert_nil actual[:default_env] + assert_nil actual[:not_production] + assert_nil actual[:production] + assert_nil actual[:development] + assert_nil actual[:test] end def test_blank_with_database_url_with_rack_env @@ -184,15 +184,15 @@ module ActiveRecord "host" => "localhost" } assert_equal expected, actual["not_production"] - assert_equal nil, actual["production"] - assert_equal nil, actual["default_env"] - assert_equal nil, actual["development"] - assert_equal nil, actual["test"] - assert_equal nil, actual[:default_env] - assert_equal nil, actual[:not_production] - assert_equal nil, actual[:production] - assert_equal nil, actual[:development] - assert_equal nil, actual[:test] + assert_nil actual["production"] + assert_nil actual["default_env"] + assert_nil actual["development"] + assert_nil actual["test"] + assert_nil actual[:default_env] + assert_nil actual[:not_production] + assert_nil actual[:production] + assert_nil actual[:development] + assert_nil actual[:test] end def test_database_url_with_ipv6_host_and_port diff --git a/activerecord/test/cases/connection_adapters/schema_cache_test.rb b/activerecord/test/cases/connection_adapters/schema_cache_test.rb index d4459603af..106323ccc9 100644 --- a/activerecord/test/cases/connection_adapters/schema_cache_test.rb +++ b/activerecord/test/cases/connection_adapters/schema_cache_test.rb @@ -12,6 +12,33 @@ module ActiveRecord assert_equal "id", @cache.primary_keys("posts") end + def test_yaml_dump_and_load + @cache.columns("posts") + @cache.columns_hash("posts") + @cache.data_sources("posts") + @cache.primary_keys("posts") + + new_cache = YAML.load(YAML.dump(@cache)) + assert_no_queries do + assert_equal 11, new_cache.columns("posts").size + assert_equal 11, new_cache.columns_hash("posts").size + assert new_cache.data_sources("posts") + assert_equal "id", new_cache.primary_keys("posts") + end + end + + def test_yaml_loads_5_1_dump + body = File.open(schema_dump_path).read + cache = YAML.load(body) + + assert_no_queries do + assert_equal 11, cache.columns("posts").size + assert_equal 11, cache.columns_hash("posts").size + assert cache.data_sources("posts") + assert_equal "id", cache.primary_keys("posts") + end + end + def test_primary_key_for_non_existent_table assert_nil @cache.primary_keys("omgponies") end @@ -45,17 +72,28 @@ module ActiveRecord @cache = Marshal.load(Marshal.dump(@cache)) - assert_equal 11, @cache.columns("posts").size - assert_equal 11, @cache.columns_hash("posts").size - assert @cache.data_sources("posts") - assert_equal "id", @cache.primary_keys("posts") + assert_no_queries do + assert_equal 11, @cache.columns("posts").size + assert_equal 11, @cache.columns_hash("posts").size + assert @cache.data_sources("posts") + assert_equal "id", @cache.primary_keys("posts") + 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 + + def schema_dump_path + "test/assets/schema_dump_5_1.yml" + end end end end 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/defaults_test.rb b/activerecord/test/cases/defaults_test.rb index fcaff38f82..6532efcf22 100644 --- a/activerecord/test/cases/defaults_test.rb +++ b/activerecord/test/cases/defaults_test.rb @@ -169,7 +169,7 @@ if current_adapter?(:Mysql2Adapter) assert_nil record.non_null_text assert_nil record.non_null_blob - assert_raises(ActiveRecord::StatementInvalid) { klass.create } + assert_raises(ActiveRecord::NotNullViolation) { klass.create } end end end diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index 0e58e65a07..c0d6ddcea7 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -301,6 +301,14 @@ class DirtyTest < ActiveRecord::TestCase assert_equal ["arr", "arr matey!"], pirate.catchphrase_change end + def test_virtual_attribute_will_change + assert_deprecated do + parrot = Parrot.create!(name: "Ruby") + parrot.send(:attribute_will_change!, :cancel_save_from_callback) + assert parrot.has_changes_to_save? + end + end + def test_association_assignment_changes_foreign_key pirate = Pirate.create!(catchphrase: "jarl") pirate.parrot = Parrot.create!(name: "Lorre") 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 7eaf31aa24..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 @@ -488,12 +493,12 @@ class FinderTest < ActiveRecord::TestCase assert_equal topics(:fourth), Topic.offset(1).second_to_last assert_equal topics(:fourth), Topic.offset(2).second_to_last assert_equal topics(:fourth), Topic.offset(3).second_to_last - assert_equal nil, Topic.offset(4).second_to_last - assert_equal nil, Topic.offset(5).second_to_last + assert_nil Topic.offset(4).second_to_last + assert_nil Topic.offset(5).second_to_last #test with limit - # assert_equal nil, Topic.limit(1).second # TODO: currently failing - assert_equal nil, Topic.limit(1).second_to_last + # assert_nil Topic.limit(1).second # TODO: currently failing + assert_nil Topic.limit(1).second_to_last end def test_second_to_last_have_primary_key_order_by_default @@ -516,15 +521,15 @@ class FinderTest < ActiveRecord::TestCase # test with offset assert_equal topics(:third), Topic.offset(1).third_to_last assert_equal topics(:third), Topic.offset(2).third_to_last - assert_equal nil, Topic.offset(3).third_to_last - assert_equal nil, Topic.offset(4).third_to_last - assert_equal nil, Topic.offset(5).third_to_last + assert_nil Topic.offset(3).third_to_last + assert_nil Topic.offset(4).third_to_last + assert_nil Topic.offset(5).third_to_last # test with limit - # assert_equal nil, Topic.limit(1).third # TODO: currently failing - assert_equal nil, Topic.limit(1).third_to_last - # assert_equal nil, Topic.limit(2).third # TODO: currently failing - assert_equal nil, Topic.limit(2).third_to_last + # assert_nil Topic.limit(1).third # TODO: currently failing + assert_nil Topic.limit(1).third_to_last + # assert_nil Topic.limit(2).third # TODO: currently failing + assert_nil Topic.limit(2).third_to_last end def test_third_to_last_have_primary_key_order_by_default @@ -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 @@ -983,7 +988,6 @@ class FinderTest < ActiveRecord::TestCase assert_equal devs[2], Developer.offset(2).first assert_equal devs[-3], Developer.offset(2).last - assert_equal devs[-3], Developer.offset(2).last assert_equal devs[-3], Developer.offset(2).order("id DESC").first end @@ -1167,7 +1171,7 @@ class FinderTest < ActiveRecord::TestCase end test "find_by returns nil if the record is missing" do - assert_equal nil, Post.find_by("1 = 0") + assert_nil Post.find_by("1 = 0") end test "find_by with associations" do @@ -1221,7 +1225,7 @@ class FinderTest < ActiveRecord::TestCase assert_equal tyre2, zyke.tyres.custom_find_by(id: tyre2.id) end - protected + private def table_with_custom_primary_key yield(Class.new(Toy) do def self.name diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index f3d0e4a1b1..61e596e208 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -7,17 +7,19 @@ require "models/binary" require "models/book" require "models/bulb" require "models/category" +require "models/post" require "models/comment" require "models/company" require "models/computer" require "models/course" require "models/developer" +require "models/dog" require "models/doubloon" require "models/joke" 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" @@ -211,9 +213,19 @@ class FixturesTest < ActiveRecord::TestCase end def test_dirty_dirty_yaml_file - assert_raise(ActiveRecord::Fixture::FormatError) do - ActiveRecord::FixtureSet.new(Account.connection, "courses", Course, FIXTURES_ROOT + "/naked/yml/courses") + fixture_path = FIXTURES_ROOT + "/naked/yml/courses" + error = assert_raise(ActiveRecord::Fixture::FormatError) do + ActiveRecord::FixtureSet.new(Account.connection, "courses", Course, fixture_path) + end + assert_equal "fixture is not a hash: #{fixture_path}.yml", error.to_s + end + + def test_yaml_file_with_one_invalid_fixture + fixture_path = FIXTURES_ROOT + "/naked/yml/courses_with_invalid_key" + error = assert_raise(ActiveRecord::Fixture::FormatError) do + ActiveRecord::FixtureSet.new(Account.connection, "courses", Course, fixture_path) end + assert_equal "fixture key is not a hash: #{fixture_path}.yml, keys: [\"two\"]", error.to_s end def test_yaml_file_with_invalid_column @@ -1000,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 @@ -1011,3 +1023,16 @@ class FixtureClassNamesTest < ActiveRecord::TestCase assert_nil fixture_class_names["unregistered_identifier"] end end + +class SameNameDifferentDatabaseFixturesTest < ActiveRecord::TestCase + fixtures :dogs, :other_dogs + + test "fixtures are properly loaded" do + # Force loading the fixtures again to reproduce issue + ActiveRecord::FixtureSet.reset_cache + create_fixtures("dogs", "other_dogs") + + assert_kind_of Dog, dogs(:sophie) + assert_kind_of OtherDog, other_dogs(:lassie) + end +end diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index f1d69a215a..5a3b8e3fb5 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -6,7 +6,6 @@ require "active_record" require "cases/test_case" require "active_support/dependencies" require "active_support/logger" -require "active_support/core_ext/string/strip" require "support/config" require "support/connection" @@ -27,9 +26,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/integration_test.rb b/activerecord/test/cases/integration_test.rb index 00457965d7..d7aa091623 100644 --- a/activerecord/test/cases/integration_test.rb +++ b/activerecord/test/cases/integration_test.rb @@ -15,7 +15,7 @@ class IntegrationTest < ActiveRecord::TestCase def test_to_param_returns_nil_if_not_persisted client = Client.new - assert_equal nil, client.to_param + assert_nil client.to_param end def test_to_param_returns_id_if_not_persisted_but_id_is_set @@ -89,7 +89,7 @@ class IntegrationTest < ActiveRecord::TestCase def test_to_param_class_method_uses_default_if_not_persisted firm = Firm.new(name: "Fancy Shirts") - assert_equal nil, firm.to_param + assert_nil firm.to_param end def test_to_param_with_no_arguments 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..5a1d066aef 100644 --- a/activerecord/test/cases/json_serialization_test.rb +++ b/activerecord/test/cases/json_serialization_test.rb @@ -101,6 +101,17 @@ class JsonSerializationTest < ActiveRecord::TestCase assert_match %r{"favorite_quote":"Constraints are liberating"}, methods_json end + def test_uses_serializable_hash_with_frozen_hash + def @contact.serializable_hash(options = nil) + super({ only: %w(name) }.freeze) + end + + json = @contact.to_json + assert_match %r{"name":"Konata Izumi"}, json + assert_no_match %r{awesome}, json + assert_no_match %r{age}, json + end + def test_uses_serializable_hash_with_only_option def @contact.serializable_hash(options = nil) super(only: %w(name)) @@ -243,7 +254,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 9b0ec54aa7..23095618a4 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 @@ -536,7 +536,10 @@ unless in_memory_db? Person.transaction do person = Person.find 1 old, person.first_name = person.first_name, "fooman" - person.lock! + # Locking a dirty record is deprecated + assert_deprecated do + person.lock! + end assert_equal old, person.first_name end end @@ -579,7 +582,7 @@ unless in_memory_db? assert first.end > second.end end - protected + private def duel(zzz = 5) t0, t1, t2, t3 = nil, nil, nil, nil diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb index bdb90eaa74..48cfe89882 100644 --- a/activerecord/test/cases/migration/change_schema_test.rb +++ b/activerecord/test/cases/migration/change_schema_test.rb @@ -43,7 +43,7 @@ module ActiveRecord t.column :foo, :string, null: false end - assert_raises(ActiveRecord::StatementInvalid) do + assert_raises(ActiveRecord::NotNullViolation) do connection.execute "insert into testings (foo) values (NULL)" end end @@ -233,7 +233,7 @@ module ActiveRecord end connection.add_column :testings, :bar, :string, null: false - assert_raise(ActiveRecord::StatementInvalid) do + assert_raise(ActiveRecord::NotNullViolation) do connection.execute "insert into testings (foo, bar) values ('hello', NULL)" end end @@ -244,12 +244,16 @@ module ActiveRecord t.column :foo, :string end - con = connection - connection.execute "insert into testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}) values (1, 'hello')" - assert_nothing_raised { connection.add_column :testings, :bar, :string, null: false, default: "default" } + quoted_id = connection.quote_column_name("id") + quoted_foo = connection.quote_column_name("foo") + quoted_bar = connection.quote_column_name("bar") + connection.execute("insert into testings (#{quoted_id}, #{quoted_foo}) values (1, 'hello')") + assert_nothing_raised do + connection.add_column :testings, :bar, :string, null: false, default: "default" + end - assert_raises(ActiveRecord::StatementInvalid) do - connection.execute "insert into testings (#{con.quote_column_name('id')}, #{con.quote_column_name('foo')}, #{con.quote_column_name('bar')}) values (2, 'hello', NULL)" + assert_raises(ActiveRecord::NotNullViolation) do + connection.execute("insert into testings (#{quoted_id}, #{quoted_foo}, #{quoted_bar}) values (2, 'hello', NULL)") end end @@ -405,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/compatibility_test.rb b/activerecord/test/cases/migration/compatibility_test.rb index 0a4b604601..7a80bfb899 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,119 @@ 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 - assert_deprecated do - migration.migrate :up +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| + t.references :legacy_ref end end + }.new + + @migration.migrate(:up) + + legacy_pk = LegacyPrimaryKey.columns_hash["id"] + assert_not legacy_pk.bigint? + assert_not legacy_pk.null + + legacy_ref = LegacyPrimaryKey.columns_hash["legacy_ref_id"] + assert_not legacy_ref.bigint? + + 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 cab2069754..2258290a3c 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 @@ -76,7 +94,7 @@ if ActiveRecord::Base.connection.supports_foreign_keys? end def test_add_foreign_key_with_non_standard_primary_key - with_example_table @connection, "space_shuttles", "pk integer PRIMARY KEY" do + with_example_table @connection, "space_shuttles", "pk BIGINT PRIMARY KEY" do @connection.add_foreign_key(:astronauts, :space_shuttles, column: "rocket_id", primary_key: "pk", name: "custom_pk") @@ -101,7 +119,7 @@ if ActiveRecord::Base.connection.supports_foreign_keys? fk = foreign_keys.first if current_adapter?(:Mysql2Adapter) # ON DELETE RESTRICT is the default on MySQL - assert_equal nil, fk.on_delete + assert_nil fk.on_delete else assert_equal :restrict, fk.on_delete end @@ -229,7 +247,7 @@ if ActiveRecord::Base.connection.supports_foreign_keys? create_table("cities") { |t| } create_table("houses") do |t| - t.column :city_id, :integer + t.references :city end add_foreign_key :houses, :cities, column: "city_id" @@ -261,7 +279,7 @@ if ActiveRecord::Base.connection.supports_foreign_keys? create_table(:schools) create_table(:classes) do |t| - t.column :school_id, :integer + t.references :school end add_foreign_key :classes, :schools end @@ -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/index_test.rb b/activerecord/test/cases/migration/index_test.rb index 0f975026b8..f10fcf1398 100644 --- a/activerecord/test/cases/migration/index_test.rb +++ b/activerecord/test/cases/migration/index_test.rb @@ -31,9 +31,10 @@ module ActiveRecord connection.add_index(table_name, [:foo], name: "old_idx") connection.rename_index(table_name, "old_idx", "new_idx") - # if the adapter doesn't support the indexes call, pick defaults that let the test pass - assert_not connection.index_name_exists?(table_name, "old_idx", false) - assert connection.index_name_exists?(table_name, "new_idx", true) + assert_deprecated do + assert_not connection.index_name_exists?(table_name, "old_idx", false) + assert connection.index_name_exists?(table_name, "new_idx", true) + end end def test_rename_index_too_long @@ -45,8 +46,7 @@ module ActiveRecord } assert_match(/too long; the limit is #{connection.allowed_index_name_length} characters/, e.message) - # if the adapter doesn't support the indexes call, pick defaults that let the test pass - assert connection.index_name_exists?(table_name, "old_idx", false) + assert connection.index_name_exists?(table_name, "old_idx") end def test_double_add_index @@ -63,7 +63,7 @@ module ActiveRecord def test_add_index_works_with_long_index_names connection.add_index(table_name, "foo", name: good_index_name) - assert connection.index_name_exists?(table_name, good_index_name, false) + assert connection.index_name_exists?(table_name, good_index_name) connection.remove_index(table_name, name: good_index_name) end @@ -75,7 +75,7 @@ module ActiveRecord } assert_match(/too long; the limit is #{connection.allowed_index_name_length} characters/, e.message) - assert_not connection.index_name_exists?(table_name, too_long_index_name, false) + assert_not connection.index_name_exists?(table_name, too_long_index_name) connection.add_index(table_name, "foo", name: good_index_name) end @@ -83,7 +83,7 @@ module ActiveRecord good_index_name = "x" * connection.index_name_length connection.add_index(table_name, "foo", name: good_index_name, internal: true) - assert connection.index_name_exists?(table_name, good_index_name, false) + assert connection.index_name_exists?(table_name, good_index_name) connection.remove_index(table_name, name: good_index_name) end diff --git a/activerecord/test/cases/migration/references_foreign_key_test.rb b/activerecord/test/cases/migration/references_foreign_key_test.rb index 528811db49..9418995ea0 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) @@ -42,8 +42,7 @@ if ActiveRecord::Base.connection.supports_foreign_keys? test "options hash can be passed" do @connection.change_table :testing_parents do |t| - t.integer :other_id - t.index :other_id, unique: true + t.references :other, index: { unique: true } end @connection.create_table :testings do |t| t.references :testing_parent, foreign_key: { primary_key: :other_id } @@ -61,6 +60,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| @@ -92,8 +109,7 @@ if ActiveRecord::Base.connection.supports_foreign_keys? test "foreign keys accept options when changing the table" do @connection.change_table :testing_parents do |t| - t.integer :other_id - t.index :other_id, unique: true + t.references :other, index: { unique: true } end @connection.create_table :testings @connection.change_table :testings do |t| @@ -177,18 +193,15 @@ if ActiveRecord::Base.connection.supports_foreign_keys? test "multiple foreign keys can be added to the same table" do @connection.create_table :testings do |t| - t.integer :col_1 - t.integer :col_2 - - t.foreign_key :testing_parents, column: :col_1 - t.foreign_key :testing_parents, column: :col_2 + t.references :parent1, foreign_key: { to_table: :testing_parents } + t.references :parent2, foreign_key: { to_table: :testing_parents } end - fks = @connection.foreign_keys("testings") + fks = @connection.foreign_keys("testings").sort_by(&:column) fk_definitions = fks.map { |fk| [fk.from_table, fk.to_table, fk.column] } - assert_equal([["testings", "testing_parents", "col_1"], - ["testings", "testing_parents", "col_2"]], fk_definitions) + assert_equal([["testings", "testing_parents", "parent1_id"], + ["testings", "testing_parents", "parent2_id"]], fk_definitions) end end end 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 22484ad678..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 @@ -705,7 +707,7 @@ class MigrationTest < ActiveRecord::TestCase end end - protected + private # This is needed to isolate class_attribute assignments like `table_name_prefix` # for each test case. def new_isolated_reminder_class @@ -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 eaaf50d14f..98b9c91489 100644 --- a/activerecord/test/cases/primary_keys_test.rb +++ b/activerecord/test/cases/primary_keys_test.rb @@ -7,6 +7,7 @@ require "models/movie" require "models/keyboard" require "models/mixed_case_monkey" require "models/dashboard" +require "models/non_primary_key" class PrimaryKeysTest < ActiveRecord::TestCase fixtures :topics, :subscribers, :movies, :mixed_case_monkeys @@ -89,6 +90,12 @@ class PrimaryKeysTest < ActiveRecord::TestCase assert_equal("John Doe", subscriberReloaded.name) end + def test_id_column_that_is_not_primary_key + NonPrimaryKey.create!(id: 100) + actual = NonPrimaryKey.find_by(id: 100) + assert_match %r{<NonPrimaryKey id: 100}, actual.inspect + end + def test_find_with_more_than_one_string_key assert_equal 2, Subscriber.find(subscribers(:first).nick, subscribers(:second).nick).length end @@ -113,44 +120,45 @@ class PrimaryKeysTest < ActiveRecord::TestCase def test_delete_should_quote_pkey assert_nothing_raised { MixedCaseMonkey.delete(1) } end + def test_update_counters_should_quote_pkey_and_quote_counter_columns assert_nothing_raised { MixedCaseMonkey.update_counters(1, fleaCount: 99) } end + def test_find_with_one_id_should_quote_pkey assert_nothing_raised { MixedCaseMonkey.find(1) } end + def test_find_with_multiple_ids_should_quote_pkey assert_nothing_raised { MixedCaseMonkey.find([1, 2]) } end + def test_instance_update_should_quote_pkey assert_nothing_raised { MixedCaseMonkey.find(1).save } end + def test_instance_destroy_should_quote_pkey assert_nothing_raised { MixedCaseMonkey.find(1).destroy } end - def test_supports_primary_key - assert_nothing_raised do - ActiveRecord::Base.connection.supports_primary_key? - end + def test_deprecate_supports_primary_key + assert_deprecated { ActiveRecord::Base.connection.supports_primary_key? } end - if ActiveRecord::Base.connection.supports_primary_key? - def test_primary_key_returns_value_if_it_exists - klass = Class.new(ActiveRecord::Base) do - self.table_name = "developers" - end - - assert_equal "id", klass.primary_key + def test_primary_key_returns_value_if_it_exists + klass = Class.new(ActiveRecord::Base) do + self.table_name = "developers" end - def test_primary_key_returns_nil_if_it_does_not_exist - klass = Class.new(ActiveRecord::Base) do - self.table_name = "developers_projects" - end + assert_equal "id", klass.primary_key + end - assert_nil klass.primary_key + def test_primary_key_returns_nil_if_it_does_not_exist + klass = Class.new(ActiveRecord::Base) do + self.table_name = "developers_projects" end + + assert_nil klass.primary_key end def test_quoted_primary_key_after_set_primary_key @@ -216,6 +224,43 @@ class PrimaryKeyWithNoConnectionTest < ActiveRecord::TestCase end end +class PrimaryKeyWithAutoIncrementTest < ActiveRecord::TestCase + self.use_transactional_tests = false + + class AutoIncrement < ActiveRecord::Base + end + + def setup + @connection = ActiveRecord::Base.connection + end + + def teardown + @connection.drop_table(:auto_increments, if_exists: true) + end + + def test_primary_key_with_auto_increment + @connection.create_table(:auto_increments, id: :integer, auto_increment: true, force: true) + assert_auto_incremented + end + + def test_primary_key_with_auto_increment_and_bigint + @connection.create_table(:auto_increments, id: :bigint, auto_increment: true, force: true) + assert_auto_incremented + end + + private + def assert_auto_incremented + record1 = AutoIncrement.create! + assert_not_nil record1.id + + record1.destroy + + record2 = AutoIncrement.create! + assert_not_nil record2.id + assert_operator record2.id, :>, record1.id + end +end + class PrimaryKeyAnyTypeTest < ActiveRecord::TestCase include SchemaDumpingHelper @@ -260,6 +305,10 @@ class CompositePrimaryKeyTest < ActiveRecord::TestCase t.string :region t.integer :code end + @connection.create_table(:barcodes_reverse, primary_key: ["code", "region"], force: true) do |t| + t.string :region + t.integer :code + end end def teardown @@ -270,6 +319,11 @@ class CompositePrimaryKeyTest < ActiveRecord::TestCase assert_equal ["region", "code"], @connection.primary_keys("barcodes") end + def test_composite_primary_key_out_of_order + skip if current_adapter?(:SQLite3Adapter) + assert_equal ["code", "region"], @connection.primary_keys("barcodes_reverse") + end + def test_primary_key_issues_warning model = Class.new(ActiveRecord::Base) do def self.table_name @@ -282,42 +336,47 @@ class CompositePrimaryKeyTest < ActiveRecord::TestCase assert_match(/WARNING: Active Record does not support composite primary key\./, warning) end - def test_collectly_dump_composite_primary_key + def test_dumping_composite_primary_key schema = dump_table_schema "barcodes" assert_match %r{create_table "barcodes", primary_key: \["region", "code"\]}, schema end + + def test_dumping_composite_primary_key_out_of_order + skip if current_adapter?(:SQLite3Adapter) + schema = dump_table_schema "barcodes_reverse" + assert_match %r{create_table "barcodes_reverse", primary_key: \["code", "region"\]}, schema + end end -if current_adapter?(:Mysql2Adapter) - class PrimaryKeyBigintNilDefaultTest < 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(:bigint_defaults, id: :bigint, default: nil, force: true) - end + def setup + @connection = ActiveRecord::Base.connection + end - def teardown - @connection.drop_table :bigint_defaults, if_exists: true - end + def teardown + @connection.drop_table :int_defaults, if_exists: true + end - test "primary key with bigint allows default override via nil" do - column = @connection.columns(:bigint_defaults).find { |c| c.name == "id" } - assert column.bigint? - 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 bigint default nil" do - schema = dump_table_schema "bigint_defaults" - assert_match %r{create_table "bigint_defaults", id: :bigint, 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 if current_adapter?(:PostgreSQLAdapter, :Mysql2Adapter) - class PrimaryKeyBigSerialTest < ActiveRecord::TestCase + class PrimaryKeyIntegerTest < ActiveRecord::TestCase include SchemaDumpingHelper self.use_transactional_tests = false @@ -327,46 +386,55 @@ if current_adapter?(:PostgreSQLAdapter, :Mysql2Adapter) setup do @connection = ActiveRecord::Base.connection - if current_adapter?(:PostgreSQLAdapter) - @connection.create_table(:widgets, id: :bigserial, force: true) - else - @connection.create_table(:widgets, id: :bigint, force: true) - end + @pk_type = current_adapter?(:PostgreSQLAdapter) ? :serial : :integer end teardown do @connection.drop_table :widgets, if_exists: true - Widget.reset_column_information end - test "primary key column type with bigserial" do - column_type = Widget.type_for_attribute(Widget.primary_key) - assert_equal :integer, column_type.type - assert_equal 8, column_type.limit + test "primary key column type with serial/integer" do + @connection.create_table(:widgets, id: @pk_type, force: true) + column = @connection.columns(:widgets).find { |c| c.name == "id" } + assert_equal :integer, column.type + assert_not column.bigint? end - test "primary key with bigserial are automatically numbered" do + test "primary key with serial/integer are automatically numbered" do + @connection.create_table(:widgets, id: @pk_type, force: true) widget = Widget.create! assert_not_nil widget.id end - test "schema dump primary key with bigserial" do + test "schema dump primary key with serial/integer" do + @connection.create_table(:widgets, id: @pk_type, force: true) schema = dump_table_schema "widgets" - if current_adapter?(:PostgreSQLAdapter) - assert_match %r{create_table "widgets", id: :bigserial, force: :cascade}, schema - else - assert_match %r{create_table "widgets", id: :bigint, force: :cascade}, schema - end + assert_match %r{create_table "widgets", id: :#{@pk_type}, force: :cascade}, schema end if current_adapter?(:Mysql2Adapter) test "primary key column type with options" do - @connection.create_table(:widgets, id: :primary_key, limit: 8, unsigned: true, force: true) + @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 8, column.limit + assert_not column.bigint? assert column.unsigned? + + schema = dump_table_schema "widgets" + assert_match %r{create_table "widgets", id: :integer, unsigned: true, force: :cascade}, schema + end + + test "bigint primary key with unsigned" do + @connection.create_table(:widgets, id: :bigint, unsigned: true, force: true) + column = @connection.columns(:widgets).find { |c| c.name == "id" } + assert column.auto_increment? + assert_equal :integer, column.type + assert column.bigint? + assert column.unsigned? + + schema = dump_table_schema "widgets" + assert_match %r{create_table "widgets", id: :bigint, unsigned: true, force: :cascade}, schema end end end diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb index 90054ce83d..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.wait + started = Concurrent::Event.new + checked = Concurrent::Event.new - 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 + thread_2_connection = nil + thread = Thread.new { + thread_2_connection = ActiveRecord::Base.connection - assert_cache :off, thread_2_connection - }.call({}) + assert_equal thread_2_connection, thread_1_connection + assert_cache :off - ActiveRecord::Base.connection_pool.connections.each do |conn| - assert_cache :off, conn + middleware { + assert_cache :clean + + Task.find 1 + assert_cache :dirty + + started.set + checked.wait + + ActiveRecord::Base.clear_active_connections! + }.call({}) + } + + started.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 + + 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 @@ -202,6 +228,20 @@ class QueryCacheTest < ActiveRecord::TestCase ActiveSupport::Notifications.unsubscribe subscriber end + def test_query_cache_does_not_allow_sql_key_mutation + subscriber = ActiveSupport::Notifications.subscribe("sql.active_record") do |_, _, _, _, payload| + payload[:sql].downcase! + end + + assert_raises RuntimeError do + ActiveRecord::Base.cache do + assert_queries(1) { Task.find(1); Task.find(1) } + end + end + ensure + ActiveSupport::Notifications.unsubscribe subscriber + end + def test_cache_is_flat Task.cache do assert_queries(1) { Topic.find(1); Topic.find(1); } @@ -246,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 @@ -314,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 @@ -374,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 5dac3d064b..c1c2efb9c8 100644 --- a/activerecord/test/cases/reflection_test.rb +++ b/activerecord/test/cases/reflection_test.rb @@ -86,8 +86,8 @@ class ReflectionTest < ActiveRecord::TestCase column = @first.column_for_attribute("attribute_that_doesnt_exist") assert_instance_of ActiveRecord::ConnectionAdapters::NullColumn, column assert_equal "attribute_that_doesnt_exist", column.name - assert_equal nil, column.sql_type - assert_equal nil, column.type + assert_nil column.sql_type + assert_nil column.type end def test_non_existent_types_are_identity_types @@ -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/delegation_test.rb b/activerecord/test/cases/relation/delegation_test.rb index d2382b9bb2..49d4aeafc9 100644 --- a/activerecord/test/cases/relation/delegation_test.rb +++ b/activerecord/test/cases/relation/delegation_test.rb @@ -32,7 +32,8 @@ module ActiveRecord :exclude?, :find_all, :flat_map, :group_by, :include?, :length, :map, :none?, :one?, :partition, :reject, :reverse, :sample, :second, :sort, :sort_by, :third, - :to_ary, :to_set, :to_xml, :to_yaml, :join + :to_ary, :to_set, :to_xml, :to_yaml, :join, + :in_groups, :in_groups_of, :to_sentence, :to_formatted_s ] ARRAY_DELEGATES.each do |method| 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 716ace9686..0c94e891eb 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -442,7 +442,7 @@ class RelationTest < ActiveRecord::TestCase assert_no_queries(ignore_none: false) do assert_equal 0, Developer.none.count assert_equal 0, Developer.none.calculate(:count, nil) - assert_equal nil, Developer.none.calculate(:average, "salary") + assert_nil Developer.none.calculate(:average, "salary") end end @@ -485,28 +485,28 @@ class RelationTest < ActiveRecord::TestCase def test_null_relation_average ac = Aircraft.new assert_equal Hash.new, ac.engines.group(:car_id).average(:id) - assert_equal nil, ac.engines.average(:id) + assert_nil ac.engines.average(:id) ac.save assert_equal Hash.new, ac.engines.group(:car_id).average(:id) - assert_equal nil, ac.engines.average(:id) + assert_nil ac.engines.average(:id) end def test_null_relation_minimum ac = Aircraft.new assert_equal Hash.new, ac.engines.group(:car_id).minimum(:id) - assert_equal nil, ac.engines.minimum(:id) + assert_nil ac.engines.minimum(:id) ac.save assert_equal Hash.new, ac.engines.group(:car_id).minimum(:id) - assert_equal nil, ac.engines.minimum(:id) + assert_nil ac.engines.minimum(:id) end def test_null_relation_maximum ac = Aircraft.new assert_equal Hash.new, ac.engines.group(:car_id).maximum(:id) - assert_equal nil, ac.engines.maximum(:id) + assert_nil ac.engines.maximum(:id) ac.save assert_equal Hash.new, ac.engines.group(:car_id).maximum(:id) - assert_equal nil, ac.engines.maximum(:id) + assert_nil ac.engines.maximum(:id) end def test_null_relation_in_where_condition @@ -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 @@ -1814,7 +1787,7 @@ class RelationTest < ActiveRecord::TestCase end test "find_by returns nil if the record is missing" do - assert_equal nil, Post.all.find_by("1 = 0") + assert_nil Post.all.find_by("1 = 0") end test "find_by doesn't have implicit ordering" do diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index 007f976f2e..68193af16c 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -148,12 +148,7 @@ class SchemaDumperTest < ActiveRecord::TestCase assert_match %r{c_int_4.*limit: 4}, output end - if current_adapter?(:SQLite3Adapter) - assert_match %r{c_int_5.*limit: 5}, output - assert_match %r{c_int_6.*limit: 6}, output - assert_match %r{c_int_7.*limit: 7}, output - assert_match %r{c_int_8.*limit: 8}, output - elsif current_adapter?(:OracleAdapter) + if current_adapter?(:SQLite3Adapter, :OracleAdapter) assert_match %r{c_int_5.*limit: 5}, output assert_match %r{c_int_6.*limit: 6}, output assert_match %r{c_int_7.*limit: 7}, output @@ -266,25 +261,36 @@ class SchemaDumperTest < ActiveRecord::TestCase if current_adapter?(:PostgreSQLAdapter) def test_schema_dump_includes_bigint_default - output = standard_dump + output = dump_table_schema "defaults" assert_match %r{t\.bigint\s+"bigint_default",\s+default: 0}, output end def test_schema_dump_includes_limit_on_array_type - output = standard_dump + output = dump_table_schema "bigint_array" assert_match %r{t\.bigint\s+"big_int_data_points\",\s+array: true}, output end def test_schema_dump_allows_array_of_decimal_defaults - output = standard_dump + output = dump_table_schema "bigint_array" assert_match %r{t\.decimal\s+"decimal_array_default",\s+default: \["1.23", "3.45"\],\s+array: true}, output end def test_schema_dump_expression_indices - index_definition = standard_dump.split(/\n/).grep(/t\.index.*company_expression_index/).first.strip + index_definition = dump_table_schema("companies").split(/\n/).grep(/t\.index.*company_expression_index/).first.strip assert_equal 't.index "lower((name)::text)", name: "company_expression_index", using: :btree', index_definition end + def test_schema_dump_interval_type + output = dump_table_schema "postgresql_times" + assert_match %r{t\.interval\s+"time_interval"$}, output + assert_match %r{t\.interval\s+"scaled_time_interval",\s+precision: 6$}, output + end + + def test_schema_dump_oid_type + output = dump_table_schema "postgresql_oids" + assert_match %r{t\.oid\s+"obj_id"$}, output + end + if ActiveRecord::Base.connection.supports_extensions? def test_schema_dump_includes_extensions connection = ActiveRecord::Base.connection @@ -346,9 +352,9 @@ class SchemaDumperTest < ActiveRecord::TestCase create_table("dogs") do |t| t.column :name, :string - t.column :owner_id, :integer + t.references :owner 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 b3dc979720..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 @@ -50,7 +51,7 @@ class DefaultScopingTest < ActiveRecord::TestCase def test_default_scope_with_conditions_string assert_equal Developer.where(name: "David").map(&:id).sort, DeveloperCalledDavid.all.map(&:id).sort - assert_equal nil, DeveloperCalledDavid.create!.name + assert_nil DeveloperCalledDavid.create!.name end def test_default_scope_with_conditions_hash @@ -314,7 +315,7 @@ class DefaultScopingTest < ActiveRecord::TestCase end def test_create_attribute_overwrites_default_values - assert_equal nil, PoorDeveloperCalledJamis.create!(salary: nil).salary + assert_nil PoorDeveloperCalledJamis.create!(salary: nil).salary assert_equal 50000, PoorDeveloperCalledJamis.create!(name: "David").salary end @@ -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/scoping/relation_scoping_test.rb b/activerecord/test/cases/scoping/relation_scoping_test.rb index 47310a151e..a1ae57fdbb 100644 --- a/activerecord/test/cases/scoping/relation_scoping_test.rb +++ b/activerecord/test/cases/scoping/relation_scoping_test.rb @@ -277,7 +277,7 @@ class NestedRelationScopingTest < ActiveRecord::TestCase assert_equal "David", Developer.first.name Developer.unscoped.where("name = 'Maiha'") do - assert_equal nil, Developer.first + assert_nil Developer.first end # ensure that scoping is restored 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/statement_cache_test.rb b/activerecord/test/cases/statement_cache_test.rb index f45f63c68e..ab81ee1ad6 100644 --- a/activerecord/test/cases/statement_cache_test.rb +++ b/activerecord/test/cases/statement_cache_test.rb @@ -105,5 +105,31 @@ module ActiveRecord refute_equal book, other_book end + + def test_find_by_does_not_use_statement_cache_if_table_name_is_changed + book = Book.create(name: "my book") + + Book.find_by(name: "my book") # warming the statement cache. + + # changing the table name should change the query that is not cached. + Book.table_name = :birds + assert_nil Book.find_by(name: "my book") + ensure + Book.table_name = :books + end + + def test_find_does_not_use_statement_cache_if_table_name_is_changed + book = Book.create(name: "my book") + + Book.find(book.id) # warming the statement cache. + + # changing the table name should change the query that is not cached. + Book.table_name = :birds + assert_raise ActiveRecord::RecordNotFound do + Book.find(book.id) + end + ensure + Book.table_name = :books + end end end 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/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb index dba100f5c9..391bbe8877 100644 --- a/activerecord/test/cases/transaction_callbacks_test.rb +++ b/activerecord/test/cases/transaction_callbacks_test.rb @@ -449,6 +449,51 @@ class CallbacksOnMultipleActionsTest < ActiveRecord::TestCase end end +class CallbacksOnDestroyUpdateActionRaceTest < ActiveRecord::TestCase + class TopicWithHistory < ActiveRecord::Base + self.table_name = :topics + + def self.clear_history + @@history = [] + end + + def self.history + @@history ||= [] + end + end + + class TopicWithCallbacksOnDestroy < TopicWithHistory + after_commit(on: :destroy) { |record| record.class.history << :destroy } + end + + class TopicWithCallbacksOnUpdate < TopicWithHistory + after_commit(on: :update) { |record| record.class.history << :update } + end + + def test_trigger_once_on_multiple_deletions + TopicWithCallbacksOnDestroy.clear_history + topic = TopicWithCallbacksOnDestroy.new + topic.save + topic_clone = TopicWithCallbacksOnDestroy.find(topic.id) + topic.destroy + topic_clone.destroy + + assert_equal [:destroy], TopicWithCallbacksOnDestroy.history + end + + def test_trigger_on_update_where_row_was_deleted + TopicWithCallbacksOnUpdate.clear_history + topic = TopicWithCallbacksOnUpdate.new + topic.save + topic_clone = TopicWithCallbacksOnUpdate.find(topic.id) + topic.destroy + topic_clone.author_name = "Test Author" + topic_clone.save + + assert_equal [], TopicWithCallbacksOnUpdate.history + end +end + class TransactionEnrollmentCallbacksTest < ActiveRecord::TestCase class TopicWithoutTransactionalEnrollmentCallbacks < ActiveRecord::Base self.table_name = :topics diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb index 9b1cca8583..111495c481 100644 --- a/activerecord/test/cases/transactions_test.rb +++ b/activerecord/test/cases/transactions_test.rb @@ -206,16 +206,6 @@ class TransactionTest < ActiveRecord::TestCase assert_equal posts_count, author.posts.reload.size end - def test_cancellation_from_returning_false_in_before_filter - def @first.before_save_for_transaction - false - end - - assert_deprecated do - @first.save - end - end - def test_cancellation_from_before_destroy_rollbacks_in_destroy add_cancelling_before_destroy_with_db_side_effect_to_topic @first nbooks_before_destroy = Book.count @@ -279,7 +269,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/activemodel/test/cases/type/unsigned_integer_test.rb b/activerecord/test/cases/type/unsigned_integer_test.rb index 026cb08a06..1cd4dbc2c5 100644 --- a/activemodel/test/cases/type/unsigned_integer_test.rb +++ b/activerecord/test/cases/type/unsigned_integer_test.rb @@ -1,9 +1,8 @@ require "cases/helper" -require "active_model/type" -module ActiveModel +module ActiveRecord module Type - class UnsignedIntegerTest < ActiveModel::TestCase + class UnsignedIntegerTest < ActiveRecord::TestCase test "unsigned int max value is in range" do assert_equal(4294967295, UnsignedInteger.new.serialize(4294967295)) 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/cases/yaml_serialization_test.rb b/activerecord/test/cases/yaml_serialization_test.rb index 5192e5050a..1571b31329 100644 --- a/activerecord/test/cases/yaml_serialization_test.rb +++ b/activerecord/test/cases/yaml_serialization_test.rb @@ -95,7 +95,7 @@ class YamlSerializationTest < ActiveRecord::TestCase topic = YAML.load(yaml_fixture("rails_4_1")) assert topic.new_record? - assert_equal nil, topic.id + assert_nil topic.id assert_equal "The First Topic", topic.title assert_equal({ omg: :lol }, topic.content) end diff --git a/activerecord/test/fixtures/naked/yml/courses_with_invalid_key.yml b/activerecord/test/fixtures/naked/yml/courses_with_invalid_key.yml new file mode 100644 index 0000000000..6f9da79b45 --- /dev/null +++ b/activerecord/test/fixtures/naked/yml/courses_with_invalid_key.yml @@ -0,0 +1,3 @@ +one: + id: 1 +two: ['not a hash'] diff --git a/activerecord/test/fixtures/other_dogs.yml b/activerecord/test/fixtures/other_dogs.yml new file mode 100644 index 0000000000..b576861929 --- /dev/null +++ b/activerecord/test/fixtures/other_dogs.yml @@ -0,0 +1,2 @@ +lassie: + id: 1 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/company_in_module.rb b/activerecord/test/models/company_in_module.rb index 682f99e365..0782c1eff4 100644 --- a/activerecord/test/models/company_in_module.rb +++ b/activerecord/test/models/company_in_module.rb @@ -88,7 +88,7 @@ module MyApplication validate :check_empty_credit_limit - protected + private def check_empty_credit_limit errors.add("credit_card", :blank) if credit_card.blank? 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/non_primary_key.rb b/activerecord/test/models/non_primary_key.rb new file mode 100644 index 0000000000..1cafb09608 --- /dev/null +++ b/activerecord/test/models/non_primary_key.rb @@ -0,0 +1,2 @@ +class NonPrimaryKey < ActiveRecord::Base +end diff --git a/activerecord/test/models/other_dog.rb b/activerecord/test/models/other_dog.rb new file mode 100644 index 0000000000..418caf34be --- /dev/null +++ b/activerecord/test/models/other_dog.rb @@ -0,0 +1,5 @@ +require_dependency "models/arunit2_model" + +class OtherDog < ARUnit2Model + self.table_name = "dogs" +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/subject.rb b/activerecord/test/models/subject.rb index 29e290825e..504f68a296 100644 --- a/activerecord/test/models/subject.rb +++ b/activerecord/test/models/subject.rb @@ -5,7 +5,7 @@ class Subject < ActiveRecord::Base # as otherwise synonym test was failing after_initialize :set_email_address - protected + private def set_email_address unless persisted? self.author_email_address = "test@test.com" diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb index db04735d01..0420e2d15c 100644 --- a/activerecord/test/models/topic.rb +++ b/activerecord/test/models/topic.rb @@ -73,7 +73,7 @@ class Topic < ActiveRecord::Base write_attribute(:approved, val) end - protected + private def default_written_on self.written_on = Time.now unless attribute_present?("written_on") 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/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb index 15ba2d67ab..d6cc4e2d6e 100644 --- a/activerecord/test/schema/postgresql_specific_schema.rb +++ b/activerecord/test/schema/postgresql_specific_schema.rb @@ -23,16 +23,24 @@ ActiveRecord::Schema.define do t.string :char2, limit: 50, default: "a varchar field" t.text :char3, default: "a text field" t.bigint :bigint_default, default: -> { "0::bigint" } - t.text :multiline_default, default: '--- [] + t.text :multiline_default, default: "--- [] -' +" end - %w(postgresql_times postgresql_oids postgresql_timestamp_with_zones - postgresql_partitioned_table postgresql_partitioned_table_parent).each do |table_name| - drop_table table_name, if_exists: true + create_table :postgresql_times, force: true do |t| + t.interval :time_interval + t.interval :scaled_time_interval, precision: 6 end + create_table :postgresql_oids, force: true do |t| + t.oid :obj_id + end + + drop_table 'postgresql_timestamp_with_zones', if_exists: true + drop_table 'postgresql_partitioned_table', if_exists: true + drop_table 'postgresql_partitioned_table_parent', if_exists: true + execute "DROP SEQUENCE IF EXISTS companies_nonstd_seq CASCADE" execute "CREATE SEQUENCE companies_nonstd_seq START 101 OWNED BY companies.id" execute "ALTER TABLE companies ALTER COLUMN id SET DEFAULT nextval('companies_nonstd_seq')" @@ -45,21 +53,6 @@ ActiveRecord::Schema.define do end execute <<_SQL - CREATE TABLE postgresql_times ( - id SERIAL PRIMARY KEY, - time_interval INTERVAL, - scaled_time_interval INTERVAL(6) - ); -_SQL - - execute <<_SQL - CREATE TABLE postgresql_oids ( - id SERIAL PRIMARY KEY, - obj_id OID - ); -_SQL - - execute <<_SQL CREATE TABLE postgresql_timestamp_with_zones ( id SERIAL PRIMARY KEY, time TIMESTAMP WITH TIME ZONE diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index d889f46031..d1acf4524f 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -54,8 +54,8 @@ ActiveRecord::Schema.define do create_table :authors, force: true do |t| t.string :name, null: false - t.integer :author_address_id - t.integer :author_address_extra_id + t.references :author_address + t.references :author_address_extra t.string :organization_id t.string :owned_essay_id end @@ -88,7 +88,7 @@ ActiveRecord::Schema.define do end create_table :books, force: true do |t| - t.integer :author_id + t.references :author t.string :format t.column :name, :string t.column :status, :integer, default: 0 @@ -126,6 +126,9 @@ ActiveRecord::Schema.define do t.timestamps null: false end + create_table :old_cars, id: :integer, force: true do |t| + end + create_table :carriers, force: true create_table :categories, force: true do |t| @@ -303,7 +306,7 @@ ActiveRecord::Schema.define do end create_table :engines, force: true do |t| - t.integer :car_id + t.references :car, index: false end create_table :entrants, force: true do |t| @@ -326,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 @@ -571,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 @@ -651,8 +664,8 @@ ActiveRecord::Schema.define do end create_table :posts, force: true do |t| - t.integer :author_id - t.string :title, null: false + t.references :author + t.string :title, null: false # use VARCHAR2(4000) instead of CLOB datatype as CLOB data type has many limitations in # Oracle SELECT WHERE clause which causes many unit test failures if current_adapter?(:OracleAdapter) @@ -1001,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.integer :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| @@ -1028,6 +1039,10 @@ ActiveRecord::Schema.define do create_table :test_with_keyword_column_name, force: true do |t| t.string :desc end + + create_table :non_primary_keys, force: true, id: false do |t| + t.integer :id + end end Course.connection.create_table :courses, force: true do |t| @@ -1047,3 +1062,5 @@ Professor.connection.create_table :courses_professors, id: false, force: true do t.references :course t.references :professor end + +OtherDog.connection.create_table :dogs, force: true 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/activerecord/test/support/connection.rb b/activerecord/test/support/connection.rb index c9260398e2..bc5af36a28 100644 --- a/activerecord/test/support/connection.rb +++ b/activerecord/test/support/connection.rb @@ -2,6 +2,7 @@ require "active_support/logger" require "models/college" require "models/course" require "models/professor" +require "models/other_dog" module ARTest def self.connection_name diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 10095ee1bd..2fb7f29d73 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,73 +1,152 @@ -* Remove deprecated class `ActiveSupport::Concurrency::Latch` +* Deprecate `.halt_callback_chains_on_return_false`. + + *Rafael Mendonça França* + +* Remove deprecated behavior that halts callbacks when the return is false. + + *Rafael Mendonça França* + +* Deprecate passing string to `:if` and `:unless` conditional options + on `set_callback` and `skip_callback`. + + *Ryuta Kamizono* + +* Raise `ArgumentError` when passing string to define callback. + + *Ryuta Kamizono* + +* 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. + + *utilum* + +* Change return value of `NilClass#duplicable?`, `FalseClass#duplicable?`, + `TrueClass#duplicable?`, `Symbol#duplicable?` and `Numeric#duplicable?` + to true with Ruby 2.4+. These classes can dup with Ruby 2.4+. + + *Yuji Yaginuma* + +* Remove deprecated class `ActiveSupport::Concurrency::Latch`. *Andrew White* -* Remove deprecated separator argument from `parameterize` +* Remove deprecated separator argument from `parameterize`. *Andrew White* -* Remove deprecated method `Numeric#to_formatted_s` +* Remove deprecated method `Numeric#to_formatted_s`. *Andrew White* -* Remove deprecated method `alias_method_chain` +* Remove deprecated method `alias_method_chain`. *Andrew White* -* Remove deprecated constant `MissingSourceFile` +* Remove deprecated constant `MissingSourceFile`. *Andrew White* * Remove deprecated methods `Module.qualified_const_defined?`, - `Module.qualified_const_get` and `Module.qualified_const_set` + `Module.qualified_const_get` and `Module.qualified_const_set`. *Andrew White* -* Remove deprecated `:prefix` option from `number_to_human_size` +* Remove deprecated `:prefix` option from `number_to_human_size`. *Andrew White* -* Remove deprecated method `ActiveSupport::HashWithIndifferentAccess.new_from_hash_copying_default` +* Remove deprecated method `ActiveSupport::HashWithIndifferentAccess.new_from_hash_copying_default`. *Andrew White* -* Remove deprecated file `active_support/core_ext/time/marshal.rb` +* Remove deprecated file `active_support/core_ext/time/marshal.rb`. *Andrew White* -* Remove deprecated file `active_support/core_ext/struct.rb` +* Remove deprecated file `active_support/core_ext/struct.rb`. *Andrew White* -* Remove deprecated file `active_support/core_ext/module/method_transplanting.rb` +* Remove deprecated file `active_support/core_ext/module/method_transplanting.rb`. *Andrew White* -* Remove deprecated method `Module.local_constants` +* Remove deprecated method `Module.local_constants`. *Andrew White* -* Remove deprecated file `active_support/core_ext/kernel/debugger.rb` +* Remove deprecated file `active_support/core_ext/kernel/debugger.rb`. *Andrew White* -* Remove deprecated method `ActiveSupport::Cache::Store#namespaced_key` +* Remove deprecated method `ActiveSupport::Cache::Store#namespaced_key`. *Andrew White* -* Remove deprecated method `ActiveSupport::Cache::Strategy::LocalCache::LocalStore#set_cache_value` +* Remove deprecated method `ActiveSupport::Cache::Strategy::LocalCache::LocalStore#set_cache_value`. *Andrew White* -* Remove deprecated method `ActiveSupport::Cache::MemCacheStore#escape_key` +* Remove deprecated method `ActiveSupport::Cache::MemCacheStore#escape_key`. *Andrew White* -* Remove deprecated method `ActiveSupport::Cache::FileStore#key_file_path` +* Remove deprecated method `ActiveSupport::Cache::FileStore#key_file_path`. *Andrew White* -* Ensure duration parsing is consistent across DST changes +* Ensure duration parsing is consistent across DST changes. Previously `ActiveSupport::Duration.parse` used `Time.current` and `Time#advance` to calculate the number of seconds in the duration 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..03e3ce821a 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 @@ -80,11 +80,15 @@ module ActiveSupport cattr_accessor :test_order # :nodoc: def self.halt_callback_chains_on_return_false - Callbacks.halt_and_display_warning_on_return_false + ActiveSupport::Deprecation.warn(<<-MSG.squish) + ActiveSupport.halt_callback_chains_on_return_false is deprecated and will be removed in Rails 5.2. + MSG end def self.halt_callback_chains_on_return_false=(value) - Callbacks.halt_and_display_warning_on_return_false = value + ActiveSupport::Deprecation.warn(<<-MSG.squish) + ActiveSupport.halt_callback_chains_on_return_false= is deprecated and will be removed in Rails 5.2. + MSG end def self.to_time_preserves_timezone 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 8f0a954882..4d8c2046e8 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -6,7 +6,6 @@ require "active_support/core_ext/numeric/bytes" require "active_support/core_ext/numeric/time" require "active_support/core_ext/object/to_param" require "active_support/core_ext/string/inflections" -require "active_support/core_ext/string/strip" module ActiveSupport # See ActiveSupport::Cache::Store for documentation. @@ -71,8 +70,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) @@ -471,12 +470,12 @@ module ActiveSupport raise NotImplementedError.new("#{self.class.name} does not support clear") end - protected + private # Adds the namespace defined in the options to a pattern designed to # match keys. Implementations that support delete_matched should call # this method to translate a pattern that matches names into one that # matches namespaced keys. - def key_matcher(pattern, options) + def key_matcher(pattern, options) # :doc: prefix = options[:namespace].is_a?(Proc) ? options[:namespace].call : options[:namespace] if prefix source = pattern.source @@ -493,25 +492,24 @@ module ActiveSupport # Reads an entry from the cache implementation. Subclasses must implement # this method. - def read_entry(key, options) # :nodoc: + def read_entry(key, options) raise NotImplementedError.new end # Writes an entry to the cache implementation. Subclasses must implement # this method. - def write_entry(key, entry, options) # :nodoc: + def write_entry(key, entry, options) raise NotImplementedError.new end # Deletes an entry from the cache implementation. Subclasses must # implement this method. - def delete_entry(key, options) # :nodoc: + def delete_entry(key, options) raise NotImplementedError.new end - private # Merges the default options with ones specific to a method call. - def merged_options(call_options) # :nodoc: + def merged_options(call_options) if call_options options.merge(call_options) else @@ -522,7 +520,7 @@ module ActiveSupport # Expands key to be a consistent string value. Invokes +cache_key+ if # object responds to +cache_key+. Otherwise, +to_param+ method will be # called. If the key is a Hash, then keys will be sorted alphabetically. - def expanded_key(key) # :nodoc: + def expanded_key(key) return key.cache_key.to_s if key.respond_to?(:cache_key) case key diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb index 34456a594f..d5c8585816 100644 --- a/activesupport/lib/active_support/cache/file_store.rb +++ b/activesupport/lib/active_support/cache/file_store.rb @@ -66,7 +66,7 @@ module ActiveSupport end end - protected + private def read_entry(key, options) if File.exist?(key) @@ -98,9 +98,8 @@ module ActiveSupport end end - private # Lock a file for a block so only one process can modify it at a time. - def lock_file(file_name, &block) # :nodoc: + def lock_file(file_name, &block) if File.exist?(file_name) File.open(file_name, "r+") do |f| begin diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb index e99d65bdf2..e09cee3335 100644 --- a/activesupport/lib/active_support/cache/mem_cache_store.rb +++ b/activesupport/lib/active_support/cache/mem_cache_store.rb @@ -26,7 +26,7 @@ module ActiveSupport class MemCacheStore < Store # Provide support for raw values in the local cache strategy. module LocalCacheWithRaw # :nodoc: - protected + private def read_entry(key, options) entry = super if options[:raw] && local_cache && entry @@ -35,7 +35,7 @@ module ActiveSupport entry end - def write_entry(key, entry, options) # :nodoc: + def write_entry(key, entry, options) if options[:raw] && local_cache raw_entry = Entry.new(entry.value.to_s) raw_entry.expires_at = entry.expires_at @@ -143,14 +143,14 @@ module ActiveSupport @data.stats end - protected + private # Read an entry from the cache. - def read_entry(key, options) # :nodoc: + def read_entry(key, options) rescue_error_with(nil) { deserialize_entry(@data.get(key, options)) } end # Write an entry to the cache. - def write_entry(key, entry, options) # :nodoc: + def write_entry(key, entry, options) method = options && options[:unless_exist] ? :add : :set value = options[:raw] ? entry.value.to_s : entry expires_in = options[:expires_in].to_i @@ -164,12 +164,10 @@ module ActiveSupport end # Delete an entry from the cache. - def delete_entry(key, options) # :nodoc: + def delete_entry(key, options) rescue_error_with(false) { @data.delete(key) } end - private - # Memcache keys are binaries. So we need to force their encoding to binary # before applying the regular expression to ensure we are escaping all # characters properly. diff --git a/activesupport/lib/active_support/cache/memory_store.rb b/activesupport/lib/active_support/cache/memory_store.rb index 968e72d765..fea072d91c 100644 --- a/activesupport/lib/active_support/cache/memory_store.rb +++ b/activesupport/lib/active_support/cache/memory_store.rb @@ -104,15 +104,15 @@ module ActiveSupport @monitor.synchronize(&block) end - protected + private PER_ENTRY_OVERHEAD = 240 - def cached_size(key, entry) # :nodoc: + def cached_size(key, entry) key.to_s.bytesize + entry.size + PER_ENTRY_OVERHEAD end - def read_entry(key, options) # :nodoc: + def read_entry(key, options) entry = @data[key] synchronize do if entry @@ -124,7 +124,7 @@ module ActiveSupport entry end - def write_entry(key, entry, options) # :nodoc: + def write_entry(key, entry, options) entry.dup_value! synchronize do old_entry = @data[key] @@ -141,7 +141,7 @@ module ActiveSupport end end - def delete_entry(key, options) # :nodoc: + def delete_entry(key, options) synchronize do @key_access.delete(key) entry = @data.delete(key) @@ -150,8 +150,6 @@ module ActiveSupport end end - private - def modify_value(name, amount, options) synchronize do options = merged_options(options) diff --git a/activesupport/lib/active_support/cache/null_store.rb b/activesupport/lib/active_support/cache/null_store.rb index 0564ce5312..550659fc56 100644 --- a/activesupport/lib/active_support/cache/null_store.rb +++ b/activesupport/lib/active_support/cache/null_store.rb @@ -25,15 +25,15 @@ module ActiveSupport def delete_matched(matcher, options = nil) end - protected - def read_entry(key, options) # :nodoc: + private + def read_entry(key, options) end - def write_entry(key, entry, options) # :nodoc: + def write_entry(key, entry, options) true end - def delete_entry(key, options) # :nodoc: + def delete_entry(key, options) false end end diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb index 7652cae201..672eb2bb80 100644 --- a/activesupport/lib/active_support/cache/strategy/local_cache.rb +++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb @@ -105,8 +105,8 @@ module ActiveSupport value end - protected - def read_entry(key, options) # :nodoc: + private + def read_entry(key, options) if cache = local_cache cache.fetch_entry(key) { super } else @@ -114,17 +114,17 @@ module ActiveSupport end end - def write_entry(key, entry, options) # :nodoc: + def write_entry(key, entry, options) local_cache.write_entry(key, entry, options) if local_cache super end - def delete_entry(key, options) # :nodoc: + def delete_entry(key, options) local_cache.delete_entry(key, options) if local_cache super end - def write_cache_value(name, value, options) # :nodoc: + def write_cache_value(name, value, options) name = normalize_key(name, options) cache = local_cache cache.mute do @@ -136,8 +136,6 @@ module ActiveSupport end end - private - def local_cache_key @local_cache_key ||= "#{self.class.name.underscore}_local_cache_#{object_id}".gsub(/[\/-]/, "_").to_sym end diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index af8ddb176f..ea71569ca8 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -4,7 +4,6 @@ require "active_support/core_ext/array/extract_options" require "active_support/core_ext/class/attribute" require "active_support/core_ext/kernel/reporting" require "active_support/core_ext/kernel/singleton_class" -require "active_support/core_ext/module/attribute_accessors" require "active_support/core_ext/string/filters" require "active_support/deprecation" require "thread" @@ -69,12 +68,6 @@ module ActiveSupport CALLBACK_FILTER_TYPES = [:before, :after, :around] - # If true, Active Record and Active Model callbacks returning +false+ will - # halt the entire callback chain and display a deprecation message. - # If false, callback chains will only be halted by calling +throw :abort+. - # Defaults to +true+. - mattr_accessor(:halt_and_display_warning_on_return_false, instance_writer: false) { true } - # Runs the callbacks for the given event. # # Calls the before and around callbacks in the order they were set, yields @@ -109,16 +102,22 @@ module ActiveSupport invoke_sequence = Proc.new do skipped = nil while true - current, next_sequence = next_sequence, next_sequence.nested + current = next_sequence current.invoke_before(env) if current.final? env.value = !env.halted && (!block_given? || yield) elsif current.skip?(env) (skipped ||= []) << current + next_sequence = next_sequence.nested next else - target, block, method, *arguments = current.expand_call_template(env, invoke_sequence) - target.send(method, *arguments, &block) + next_sequence = next_sequence.nested + begin + target, block, method, *arguments = current.expand_call_template(env, invoke_sequence) + target.send(method, *arguments, &block) + ensure + next_sequence = current + end end current.invoke_after(env) skipped.pop.invoke_after(env) while skipped && skipped.first @@ -280,9 +279,9 @@ module ActiveSupport class Callback #:nodoc:# def self.build(chain, filter, kind, options) if filter.is_a?(String) - ActiveSupport::Deprecation.warn(<<-MSG.squish) - Passing string to define callback is deprecated and will be removed - in Rails 5.1 without replacement. + raise ArgumentError, <<-MSG.squish + Passing string to define a callback is not supported. See the `.set_callback` + documentation to see supported values. MSG end @@ -637,9 +636,8 @@ module ActiveSupport # set_callback :save, :before_method # # The callback can be specified as a symbol naming an instance method; as a - # proc, lambda, or block; as a string to be instance evaluated(deprecated); or as an - # object that responds to a certain method determined by the <tt>:scope</tt> - # argument to +define_callbacks+. + # proc, lambda, or block; or as an object that responds to a certain method + # determined by the <tt>:scope</tt> argument to +define_callbacks+. # # If a proc, lambda, or block is given, its body is evaluated in the context # of the current object. It can also optionally accept the current object as @@ -653,16 +651,24 @@ module ActiveSupport # # ===== Options # - # * <tt>:if</tt> - A symbol, a string or an array of symbols and strings, + # * <tt>:if</tt> - A symbol, a string (deprecated) or an array of symbols, # each naming an instance method or a proc; the callback will be called # only when they all return a true value. - # * <tt>:unless</tt> - A symbol, a string or an array of symbols and - # strings, each naming an instance method or a proc; the callback will - # be called only when they all return a false value. + # * <tt>:unless</tt> - A symbol, a string (deprecated) or an array of symbols, + # each naming an instance method or a proc; the callback will be called + # only when they all return a false value. # * <tt>:prepend</tt> - If +true+, the callback will be prepended to the # existing chain rather than appended. def set_callback(name, *filter_list, &block) type, filters, options = normalize_callback_params(filter_list, block) + + if options[:if].is_a?(String) || options[:unless].is_a?(String) + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Passing string to :if and :unless conditional options is deprecated + and will be removed in Rails 5.2 without replacement. + MSG + end + self_chain = get_callbacks name mapped = filters.map do |filter| Callback.build(self_chain, filter, type, options) @@ -686,6 +692,14 @@ module ActiveSupport # already been set (unless the <tt>:raise</tt> option is set to <tt>false</tt>). def skip_callback(name, *filter_list, &block) type, filters, options = normalize_callback_params(filter_list, block) + + if options[:if].is_a?(String) || options[:unless].is_a?(String) + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Passing string to :if and :unless conditional options is deprecated + and will be removed in Rails 5.2 without replacement. + MSG + end + options[:raise] = true unless options.key?(:raise) __update_callbacks(name) do |target, chain| @@ -835,30 +849,6 @@ module ActiveSupport def set_callbacks(name, callbacks) # :nodoc: self.__callbacks = __callbacks.merge(name.to_sym => callbacks) end - - def deprecated_false_terminator # :nodoc: - Proc.new do |target, result_lambda| - terminate = true - catch(:abort) do - result = result_lambda.call if result_lambda.is_a?(Proc) - if Callbacks.halt_and_display_warning_on_return_false && result == false - display_deprecation_warning_for_false_terminator - else - terminate = false - end - end - terminate - end - end - - private - - def display_deprecation_warning_for_false_terminator - ActiveSupport::Deprecation.warn(<<-MSG.squish) - Returning `false` in Active Record and Active Model callbacks will not implicitly halt a callback chain in Rails 5.1. - To explicitly halt the callback chain, please use `throw :abort` instead. - MSG - end end end end diff --git a/activesupport/lib/active_support/core_ext.rb b/activesupport/lib/active_support/core_ext.rb index 52706c3d7a..f397f658f3 100644 --- a/activesupport/lib/active_support/core_ext.rb +++ b/activesupport/lib/active_support/core_ext.rb @@ -1,4 +1,3 @@ -DEPRECATED_FILES = ["#{File.dirname(__FILE__)}/core_ext/struct.rb"] -(Dir["#{File.dirname(__FILE__)}/core_ext/*.rb"] - DEPRECATED_FILES).each do |path| +(Dir["#{File.dirname(__FILE__)}/core_ext/*.rb"]).each do |path| require path end 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 f5f4ba61b7..d82758e40d 100644 --- a/activesupport/lib/active_support/core_ext/module/delegation.rb +++ b/activesupport/lib/active_support/core_ext/module/delegation.rb @@ -6,11 +6,12 @@ class Module # option is not used. class DelegationError < NoMethodError; end + RUBY_RESERVED_KEYWORDS = %w(alias and BEGIN begin break case class def defined? do + else elsif END end ensure false for if in module next nil not or redo rescue retry + return self super then true undef unless until when while yield) + DELEGATION_RESERVED_KEYWORDS = %w(_ arg args block) DELEGATION_RESERVED_METHOD_NAMES = Set.new( - %w(_ arg args alias and BEGIN begin block break case class def defined? do - else elsif END end ensure false for if in module next nil not or redo - rescue retry return self super then true undef unless until when while - yield) + RUBY_RESERVED_KEYWORDS + DELEGATION_RESERVED_KEYWORDS ).freeze # Provides a +delegate+ class method to easily expose contained objects' @@ -19,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 @@ -111,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 aa2282cb7e..ea81df2bd8 100644 --- a/activesupport/lib/active_support/core_ext/object/duplicable.rb +++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb @@ -1,7 +1,7 @@ #-- -# Most objects are cloneable, but not all. For example you can't dup +nil+: +# Most objects are cloneable, but not all. For example you can't dup methods: # -# nil.dup # => TypeError: can't dup NilClass +# method(:puts).dup # => TypeError: allocator undefined for Method # # Classes may signal their instances are not duplicable removing +dup+/+clone+ # or raising exceptions from them. So, to dup an arbitrary object you normally @@ -19,7 +19,7 @@ class Object # Can you safely dup this object? # - # False for +nil+, +false+, +true+, symbol, number, method objects; + # False for method objects; # true otherwise. def duplicable? true @@ -27,52 +27,78 @@ class Object end class NilClass - # +nil+ is not duplicable: - # - # nil.duplicable? # => false - # nil.dup # => TypeError: can't dup NilClass - def duplicable? - false + begin + nil.dup + rescue TypeError + + # +nil+ is not duplicable: + # + # nil.duplicable? # => false + # nil.dup # => TypeError: can't dup NilClass + def duplicable? + false + end end end class FalseClass - # +false+ is not duplicable: - # - # false.duplicable? # => false - # false.dup # => TypeError: can't dup FalseClass - def duplicable? - false + begin + false.dup + rescue TypeError + + # +false+ is not duplicable: + # + # false.duplicable? # => false + # false.dup # => TypeError: can't dup FalseClass + def duplicable? + false + end end end class TrueClass - # +true+ is not duplicable: - # - # true.duplicable? # => false - # true.dup # => TypeError: can't dup TrueClass - def duplicable? - false + begin + true.dup + rescue TypeError + + # +true+ is not duplicable: + # + # true.duplicable? # => false + # true.dup # => TypeError: can't dup TrueClass + def duplicable? + false + end end end class Symbol - # Symbols are not duplicable: - # - # :my_symbol.duplicable? # => false - # :my_symbol.dup # => TypeError: can't dup Symbol - def duplicable? - false + begin + :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: + # + # :my_symbol.duplicable? # => false + # :my_symbol.dup # => TypeError: can't dup Symbol + def duplicable? + false + end end end class Numeric - # Numbers are not duplicable: - # - # 3.duplicable? # => false - # 3.dup # => TypeError: can't dup Integer - def duplicable? - false + begin + 1.dup + rescue TypeError + + # Numbers are not duplicable: + # + # 3.duplicable? # => false + # 3.dup # => TypeError: can't dup Integer + def duplicable? + false + end end end @@ -96,3 +122,23 @@ class Method false end end + +class Complex + # Complexes are not duplicable: + # + # Complex(1).duplicable? # => false + # Complex(1).dup # => TypeError: can't copy Complex + def duplicable? + false + end +end + +class Rational + # Rationals are not duplicable: + # + # Rational(1).duplicable? # => false + # Rational(1).dup # => TypeError: can't copy Rational + def duplicable? + false + end +end 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 ad2e139ea6..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) @@ -159,9 +212,9 @@ module ActiveSupport delegate :<=>, to: :value - protected + private - def sum(sign, time = ::Time.current) #:nodoc: + def sum(sign, time = ::Time.current) parts.inject(time) do |t, (type, number)| if t.acts_like?(:time) || t.acts_like?(:date) if type == :seconds @@ -179,9 +232,7 @@ module ActiveSupport end end - private - - def method_missing(method, *args, &block) #:nodoc: + def method_missing(method, *args, &block) value.send(method, *args, &block) end end 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 5919b89338..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 @@ -280,12 +284,12 @@ module ActiveSupport _new_hash end - protected - def convert_key(key) + private + def convert_key(key) # :doc: key.kind_of?(Symbol) ? key.to_s : key end - def convert_value(value, options = {}) + def convert_value(value, options = {}) # :doc: if value.is_a? Hash if options[:for] == :to_hash value.to_hash @@ -302,7 +306,7 @@ module ActiveSupport end end - def set_defaults(target) + def set_defaults(target) # :doc: if default_proc target.default_proc = default_proc.dup else diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb index 08c9ef8f02..b94368df14 100644 --- a/activesupport/lib/active_support/i18n_railtie.rb +++ b/activesupport/lib/active_support/i18n_railtie.rb @@ -21,8 +21,6 @@ module I18n I18n::Railtie.initialize_i18n(app) end - protected - @i18n_inited = false # Setup i18n configuration. 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 ef3df1240d..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) @@ -274,7 +274,7 @@ module ActiveSupport # Go down the ancestors to check if it is owned directly. The check # stops when we reach Object or the end of ancestors tree. - constant = constant.ancestors.inject do |const, ancestor| + constant = constant.ancestors.inject(constant) do |const, ancestor| break const if ancestor == Object break ancestor if ancestor.const_defined?(name, false) const @@ -361,7 +361,7 @@ module ActiveSupport # # const_regexp("Foo::Bar::Baz") # => "Foo(::Bar(::Baz)?)?" # const_regexp("::") # => "::" - def const_regexp(camel_cased_word) #:nodoc: + def const_regexp(camel_cased_word) parts = camel_cased_word.split("::".freeze) return Regexp.escape(camel_cased_word) if parts.blank? 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/log_subscriber.rb b/activesupport/lib/active_support/log_subscriber.rb index cb6ea095b8..e2c4f33565 100644 --- a/activesupport/lib/active_support/log_subscriber.rb +++ b/activesupport/lib/active_support/log_subscriber.rb @@ -85,7 +85,7 @@ module ActiveSupport logger.error "Could not log #{name.inspect} event. #{e.class}: #{e.message} #{e.backtrace}" end - protected + private %w(info debug warn error fatal unknown).each do |level| class_eval <<-METHOD, __FILE__, __LINE__ + 1 @@ -99,7 +99,7 @@ module ActiveSupport # option is set to +true+, it also adds bold to the string. This is based # on the Highline implementation and will automatically append CLEAR to the # end of the returned String. - def color(text, color, bold = false) + def color(text, color, bold = false) # :doc: return text unless colorize_logging color = self.class.const_get(color.upcase) if color.is_a?(Symbol) bold = bold ? BOLD : "" 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/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb index 262f25b874..65d6259a06 100644 --- a/activesupport/lib/active_support/multibyte/chars.rb +++ b/activesupport/lib/active_support/multibyte/chars.rb @@ -210,9 +210,9 @@ module ActiveSupport #:nodoc: end end - protected + private - def translate_offset(byte_offset) #:nodoc: + def translate_offset(byte_offset) return nil if byte_offset.nil? return 0 if @wrapped_string == "" @@ -224,7 +224,7 @@ module ActiveSupport #:nodoc: end end - def chars(string) #:nodoc: + def chars(string) self.class.new(string) end end diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb index 7842264b39..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 @@ -358,7 +368,7 @@ module ActiveSupport private - def apply_mapping(string, mapping) #:nodoc: + def apply_mapping(string, mapping) database.codepoints string.each_codepoint.map do |codepoint| cp = database.codepoints[codepoint] 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/number_helper/number_converter.rb b/activesupport/lib/active_support/number_helper/number_converter.rb index c485a6af63..ce363287cf 100644 --- a/activesupport/lib/active_support/number_helper/number_converter.rb +++ b/activesupport/lib/active_support/number_helper/number_converter.rb @@ -139,17 +139,17 @@ module ActiveSupport @options ||= format_options.merge(opts) end - def format_options #:nodoc: + def format_options default_format_options.merge!(i18n_format_options) end - def default_format_options #:nodoc: + def default_format_options options = DEFAULTS[:format].dup options.merge!(DEFAULTS[namespace][:format]) if namespace options end - def i18n_format_options #:nodoc: + def i18n_format_options locale = opts[:locale] options = I18n.translate(:'number.format', locale: locale, default: {}).dup @@ -160,7 +160,7 @@ module ActiveSupport options end - def translate_number_value_with_default(key, i18n_options = {}) #:nodoc: + def translate_number_value_with_default(key, i18n_options = {}) I18n.translate(key, { default: default_value(key), scope: :number }.merge!(i18n_options)) end @@ -172,7 +172,7 @@ module ActiveSupport key.split(".").reduce(DEFAULTS) { |defaults, k| defaults[k.to_sym] } end - def valid_float? #:nodoc: + def valid_float? Float(number) rescue ArgumentError, TypeError false diff --git a/activesupport/lib/active_support/per_thread_registry.rb b/activesupport/lib/active_support/per_thread_registry.rb index 9e6d8d4fd8..02431704d3 100644 --- a/activesupport/lib/active_support/per_thread_registry.rb +++ b/activesupport/lib/active_support/per_thread_registry.rb @@ -45,8 +45,8 @@ module ActiveSupport Thread.current[@per_thread_registry_key] ||= new end - protected - def method_missing(name, *args, &block) # :nodoc: + private + def method_missing(name, *args, &block) # Caches the method definition as a singleton method of the receiver. # # By letting #delegate handle it, we avoid an enclosure that'll capture args. diff --git a/activesupport/lib/active_support/rescuable.rb b/activesupport/lib/active_support/rescuable.rb index e0190fc3b0..ee6592fb5a 100644 --- a/activesupport/lib/active_support/rescuable.rb +++ b/activesupport/lib/active_support/rescuable.rb @@ -36,7 +36,7 @@ module ActiveSupport # render xml: exception, status: 500 # end # - # protected + # private # def deny_access # ... # end 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/subscriber.rb b/activesupport/lib/active_support/subscriber.rb index 2bde575698..2924139755 100644 --- a/activesupport/lib/active_support/subscriber.rb +++ b/activesupport/lib/active_support/subscriber.rb @@ -52,11 +52,15 @@ module ActiveSupport @@subscribers ||= [] 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 :subscriber, :notifier, :namespace - def add_event_subscriber(event) + private + + def add_event_subscriber(event) # :doc: return if %w{ start finish }.include?(event.to_s) pattern = "#{event}.#{namespace}" 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 e16581d697..782fb41288 100644 --- a/activesupport/lib/active_support/xml_mini.rb +++ b/activesupport/lib/active_support/xml_mini.rb @@ -68,7 +68,17 @@ module ActiveSupport "datetime" => Proc.new { |time| Time.xmlschema(time).utc rescue ::DateTime.parse(time).utc }, "integer" => Proc.new { |integer| integer.to_i }, "float" => Proc.new { |float| float.to_f }, - "decimal" => Proc.new { |number| BigDecimal(number) }, + "decimal" => Proc.new do |number| + if String === number + begin + BigDecimal(number) + rescue ArgumentError + BigDecimal("0") + end + else + BigDecimal(number) + end + end, "boolean" => Proc.new { |boolean| %w(1 true).include?(boolean.to_s.strip) }, "string" => Proc.new { |string| string.to_s }, "yaml" => Proc.new { |yaml| YAML::load(yaml) rescue yaml }, @@ -149,7 +159,7 @@ module ActiveSupport key end - protected + private def _dasherize(key) # $2 must be a non-greedy regex for this to work @@ -158,7 +168,7 @@ module ActiveSupport end # TODO: Add support for other encodings - def _parse_binary(bin, entity) #:nodoc: + def _parse_binary(bin, entity) case entity["encoding"] when "base64" ::Base64.decode64(bin) @@ -175,8 +185,6 @@ module ActiveSupport f end - private - def current_thread_backend Thread.current[:xml_mini_backend] end 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/prepend.rb b/activesupport/test/autoloading_fixtures/prepend.rb new file mode 100644 index 0000000000..3134d1df2b --- /dev/null +++ b/activesupport/test/autoloading_fixtures/prepend.rb @@ -0,0 +1,8 @@ +class SubClassConflict +end + +class Prepend + module PrependedModule + end + prepend PrependedModule +end diff --git a/activesupport/test/autoloading_fixtures/prepend/sub_class_conflict.rb b/activesupport/test/autoloading_fixtures/prepend/sub_class_conflict.rb new file mode 100644 index 0000000000..090dda3043 --- /dev/null +++ b/activesupport/test/autoloading_fixtures/prepend/sub_class_conflict.rb @@ -0,0 +1,2 @@ +class Prepend::SubClassConflict +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/caching_test.rb b/activesupport/test/caching_test.rb index 551235e9e8..c543122d91 100644 --- a/activesupport/test/caching_test.rb +++ b/activesupport/test/caching_test.rb @@ -316,7 +316,7 @@ module CacheStoreBehavior def test_should_read_and_write_nil assert @cache.write("foo", nil) - assert_equal nil, @cache.read("foo") + assert_nil @cache.read("foo") end def test_should_read_and_write_false @@ -464,7 +464,7 @@ module CacheStoreBehavior Time.stub(:now, Time.at(time)) do result = @cache.fetch("foo") do - assert_equal nil, @cache.read("foo") + assert_nil @cache.read("foo") "baz" end assert_equal "baz", result @@ -476,7 +476,7 @@ module CacheStoreBehavior @cache.write("foo", "bar", expires_in: 60) Time.stub(:now, time + 71) do result = @cache.fetch("foo", race_condition_ttl: 10) do - assert_equal nil, @cache.read("foo") + assert_nil @cache.read("foo") "baz" end assert_equal "baz", result @@ -675,9 +675,9 @@ module LocalCacheBehavior def test_local_cache_of_read_nil @cache.with_local_cache do - assert_equal nil, @cache.read("foo") + assert_nil @cache.read("foo") @cache.send(:bypass_local_cache) { @cache.write "foo", "bar" } - assert_equal nil, @cache.read("foo") + assert_nil @cache.read("foo") end end @@ -983,7 +983,7 @@ class MemoryStoreTest < ActiveSupport::TestCase end def test_pruning_is_capped_at_a_max_time - def @cache.delete_entry (*args) + def @cache.delete_entry(*args) sleep(0.01) super end diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb index aadc40ab84..4f00afb581 100644 --- a/activesupport/test/callbacks_test.rb +++ b/activesupport/test/callbacks_test.rb @@ -23,10 +23,6 @@ module CallbacksTest method_name end - def callback_string(callback_method) - "history << [#{callback_method.to_sym.inspect}, :string]" - end - def callback_proc(callback_method) Proc.new { |model| model.history << [callback_method, :proc] } end @@ -61,7 +57,6 @@ module CallbacksTest [:before_save, :after_save].each do |callback_method| callback_method_sym = callback_method.to_sym send(callback_method, callback_symbol(callback_method_sym)) - ActiveSupport::Deprecation.silence { send(callback_method, callback_string(callback_method_sym)) } send(callback_method, callback_proc(callback_method_sym)) send(callback_method, callback_object(callback_method_sym.to_s.gsub(/_save/, ""))) send(callback_method, CallbackClass) @@ -197,10 +192,12 @@ module CallbacksTest before_save Proc.new { |r| r.history << [:before_save, :symbol] }, unless: :no before_save Proc.new { |r| r.history << "b00m" }, unless: :yes # string - before_save Proc.new { |r| r.history << [:before_save, :string] }, if: "yes" - before_save Proc.new { |r| r.history << "b00m" }, if: "no" - before_save Proc.new { |r| r.history << [:before_save, :string] }, unless: "no" - before_save Proc.new { |r| r.history << "b00m" }, unless: "yes" + ActiveSupport::Deprecation.silence do + before_save Proc.new { |r| r.history << [:before_save, :string] }, if: "yes" + before_save Proc.new { |r| r.history << "b00m" }, if: "no" + before_save Proc.new { |r| r.history << [:before_save, :string] }, unless: "no" + before_save Proc.new { |r| r.history << "b00m" }, unless: "yes" + end # Combined if and unless before_save Proc.new { |r| r.history << [:before_save, :combined_symbol] }, if: :yes, unless: :no before_save Proc.new { |r| r.history << "b00m" }, if: :yes, unless: :yes @@ -224,14 +221,54 @@ module CallbacksTest define_callbacks :save end - class AroundPerson < MySuper + class MySlate < MySuper attr_reader :history attr_accessor :save_fails + def initialize + @history = [] + end + + def save + run_callbacks :save do + raise "inside save" if save_fails + @history << "running" + end + end + + def no; false; end + def yes; true; end + + def method_missing(sym, *) + case sym + when /^log_(.*)/ + @history << $1 + nil + when /^wrap_(.*)/ + @history << "wrap_#$1" + yield + @history << "unwrap_#$1" + nil + when /^double_(.*)/ + @history << "first_#$1" + yield + @history << "second_#$1" + yield + @history << "third_#$1" + else + super + end + end + + def respond_to_missing?(sym) + sym =~ /^(log|wrap)_/ || super + end + end + + class AroundPerson < MySlate set_callback :save, :before, :nope, if: :no set_callback :save, :before, :nope, unless: :yes set_callback :save, :after, :tweedle - ActiveSupport::Deprecation.silence { set_callback :save, :before, "tweedle_dee" } set_callback :save, :before, proc { |m| m.history << "yup" } set_callback :save, :before, :nope, if: proc { false } set_callback :save, :before, :nope, unless: proc { true } @@ -242,9 +279,6 @@ module CallbacksTest set_callback :save, :around, :w0tno, if: :no set_callback :save, :around, :tweedle_deedle - def no; false; end - def yes; true; end - def nope @history << "boom" end @@ -264,10 +298,6 @@ module CallbacksTest yield end - def tweedle_dee - @history << "tweedle dee" - end - def tweedle_dum @history << "tweedle dum pre" yield @@ -283,17 +313,6 @@ module CallbacksTest yield @history << "tweedle deedle post" end - - def initialize - @history = [] - end - - def save - run_callbacks :save do - raise "inside save" if save_fails - @history << "running" - end - end end class AroundPersonResult < MySuper @@ -394,7 +413,6 @@ module CallbacksTest around = AroundPerson.new around.save assert_equal [ - "tweedle dee", "yup", "yup", "tweedle dum pre", "w0tyes before", @@ -408,6 +426,32 @@ module CallbacksTest end end + class DoubleYieldTest < ActiveSupport::TestCase + class DoubleYieldModel < MySlate + set_callback :save, :around, :wrap_outer + set_callback :save, :around, :double_trouble + set_callback :save, :around, :wrap_inner + end + + def test_double_save + double = DoubleYieldModel.new + double.save + assert_equal [ + "wrap_outer", + "first_trouble", + "wrap_inner", + "running", + "unwrap_inner", + "second_trouble", + "wrap_inner", + "running", + "unwrap_inner", + "third_trouble", + "unwrap_outer", + ], double.history + end + end + class CallStackTest < ActiveSupport::TestCase def test_tidy_call_stack around = AroundPerson.new @@ -487,7 +531,6 @@ module CallbacksTest assert_equal [], person.history person.save assert_equal [ - [:before_save, :string], [:before_save, :proc], [:before_save, :object], [:before_save, :block], @@ -495,7 +538,6 @@ module CallbacksTest [:after_save, :class], [:after_save, :object], [:after_save, :proc], - [:after_save, :string], [:after_save, :symbol] ], person.history end @@ -514,7 +556,6 @@ module CallbacksTest [:after_save, :class], [:after_save, :object], [:after_save, :proc], - [:after_save, :string], [:after_save, :symbol] ], person.history end @@ -527,7 +568,6 @@ module CallbacksTest person.save assert_equal [ [:before_save, :symbol], - [:before_save, :string], [:before_save, :proc], [:before_save, :object], [:before_save, :class], @@ -536,7 +576,6 @@ module CallbacksTest [:after_save, :class], [:after_save, :object], [:after_save, :proc], - [:after_save, :string], [:after_save, :symbol] ], person.history end @@ -830,37 +869,11 @@ module CallbacksTest end end - class CallbackFalseTerminatorWithoutConfigTest < ActiveSupport::TestCase - def test_returning_false_does_not_halt_callback_if_config_variable_is_not_set - obj = CallbackFalseTerminator.new - obj.save - assert_equal nil, obj.halted - assert obj.saved - end - end - - class CallbackFalseTerminatorWithConfigTrueTest < ActiveSupport::TestCase - def setup - ActiveSupport::Callbacks.halt_and_display_warning_on_return_false = true - end - - def test_returning_false_does_not_halt_callback_if_config_variable_is_true - obj = CallbackFalseTerminator.new - obj.save - assert_equal nil, obj.halted - assert obj.saved - end - end - - class CallbackFalseTerminatorWithConfigFalseTest < ActiveSupport::TestCase - def setup - ActiveSupport::Callbacks.halt_and_display_warning_on_return_false = false - end - - def test_returning_false_does_not_halt_callback_if_config_variable_is_false + class CallbackFalseTerminatorTest < ActiveSupport::TestCase + def test_returning_false_does_not_halt_callback obj = CallbackFalseTerminator.new obj.save - assert_equal nil, obj.halted + assert_nil obj.halted assert obj.saved end end @@ -886,7 +899,6 @@ module CallbacksTest writer.save assert_equal [ [:before_save, :symbol], - [:before_save, :string], [:before_save, :proc], [:before_save, :object], [:before_save, :class], @@ -895,7 +907,6 @@ module CallbacksTest [:after_save, :class], [:after_save, :object], [:after_save, :proc], - [:after_save, :string], [:after_save, :symbol] ], writer.history end @@ -987,7 +998,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 @@ -1109,14 +1120,6 @@ module CallbacksTest assert_equal 1, calls.length end - def test_add_eval - calls = [] - klass = ActiveSupport::Deprecation.silence { build_class("bar") } - klass.class_eval { define_method(:bar) { calls << klass } } - klass.new.run - assert_equal 1, calls.length - end - def test_skip_class # removes one at a time calls = [] callback = Class.new { @@ -1151,7 +1154,7 @@ module CallbacksTest def test_skip_string # raises error calls = [] - klass = ActiveSupport::Deprecation.silence { build_class("bar") } + klass = build_class(:bar) klass.class_eval { define_method(:bar) { calls << klass } } assert_raises(ArgumentError) { klass.skip "bar" } klass.new.run @@ -1178,11 +1181,22 @@ module CallbacksTest end class DeprecatedWarningTest < ActiveSupport::TestCase - def test_deprecate_string_callback + def test_deprecate_string_conditional_options + klass = Class.new(Record) + + assert_deprecated { klass.before_save :tweedle, if: "true" } + assert_deprecated { klass.after_save :tweedle, unless: "false" } + assert_deprecated { klass.skip_callback :save, :before, :tweedle, if: "true" } + assert_deprecated { klass.skip_callback :save, :after, :tweedle, unless: "false" } + end + end + + class NotPermittedStringCallbackTest < ActiveSupport::TestCase + def test_passing_string_callback_is_not_permitted klass = Class.new(Record) - assert_deprecated do - klass.send :before_save, "tweedle_dee" + assert_raises(ArgumentError) do + klass.before_save "tweedle" end end end diff --git a/activesupport/test/class_cache_test.rb b/activesupport/test/class_cache_test.rb index c618fea81a..004b4dc9ce 100644 --- a/activesupport/test/class_cache_test.rb +++ b/activesupport/test/class_cache_test.rb @@ -61,7 +61,7 @@ module ActiveSupport def test_safe_get_constantizes_doesnt_fail_on_invalid_names assert @cache.empty? - assert_equal nil, @cache.safe_get("OmgTotallyInvalidConstantName") + assert_nil @cache.safe_get("OmgTotallyInvalidConstantName") end def test_new_rejects_strings diff --git a/activesupport/test/constantize_test_cases.rb b/activesupport/test/constantize_test_cases.rb index af2db8c991..32b720bcbb 100644 --- a/activesupport/test/constantize_test_cases.rb +++ b/activesupport/test/constantize_test_cases.rb @@ -73,6 +73,11 @@ module ConstantizeTestCases yield("RaisesNoMethodError") end end + + with_autoloading_fixtures do + yield("Prepend::SubClassConflict") + assert_equal "constant", defined?(Prepend::SubClassConflict) + end end def run_safe_constantize_tests_on 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 81777889ec..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? @@ -376,7 +380,7 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase assert_equal 1, DateTime.civil(2000) <=> Time.utc(1999, 12, 31, 23, 59, 59).to_s assert_equal 0, DateTime.civil(2000) <=> Time.utc(2000, 1, 1, 0, 0, 0).to_s assert_equal(-1, DateTime.civil(2000) <=> Time.utc(2000, 1, 1, 0, 0, 1).to_s) - assert_equal nil, DateTime.civil(2000) <=> "Invalid as Time" + assert_nil DateTime.civil(2000) <=> "Invalid as Time" end def test_compare_with_integer diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb index 26a9ea4aee..6a275d1d5b 100644 --- a/activesupport/test/core_ext/duration_test.rb +++ b/activesupport/test/core_ext/duration_test.rb @@ -21,7 +21,7 @@ class DurationTest < ActiveSupport::TestCase end def test_instance_of - assert 1.minute.instance_of?(Fixnum) + assert 1.minute.instance_of?(1.class) assert 2.days.instance_of?(ActiveSupport::Duration) assert !3.second.instance_of?(Numeric) end @@ -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/enumerable_test.rb b/activesupport/test/core_ext/enumerable_test.rb index 5b839f1032..4f1ab993b8 100644 --- a/activesupport/test/core_ext/enumerable_test.rb +++ b/activesupport/test/core_ext/enumerable_test.rb @@ -172,7 +172,7 @@ class EnumerableTests < ActiveSupport::TestCase payments.index_by(&:price)) assert_equal Enumerator, payments.index_by.class if Enumerator.method_defined? :size - assert_equal nil, payments.index_by.size + assert_nil payments.index_by.size assert_equal 42, (1..42).index_by.size end assert_equal({ 5 => Payment.new(5), 15 => Payment.new(15), 10 => Payment.new(10) }, diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index 8b816b3aad..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 @@ -390,8 +390,8 @@ class HashExtTest < ActiveSupport::TestCase assert_equal 1, hash[:a] assert_equal true, hash[:b] assert_equal false, hash[:c] - assert_equal nil, hash[:d] - assert_equal nil, hash[:e] + assert_nil hash[:d] + assert_nil hash[:e] end def test_indifferent_reading_with_nonnil_default @@ -404,7 +404,7 @@ class HashExtTest < ActiveSupport::TestCase assert_equal 1, hash[:a] assert_equal true, hash[:b] assert_equal false, hash[:c] - assert_equal nil, hash[:d] + assert_nil hash[:d] assert_equal 1, hash[:e] end @@ -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 @@ -933,8 +943,8 @@ class HashExtTest < ActiveSupport::TestCase extracted = original.extract!(:a, :x) assert_equal expected, extracted - assert_equal nil, extracted[:a] - assert_equal nil, extracted[:x] + assert_nil extracted[:a] + assert_nil extracted[:x] end def test_indifferent_extract @@ -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 @@ -1595,7 +1605,7 @@ class HashToXmlTest < ActiveSupport::TestCase def test_should_return_nil_if_no_key_is_supplied hash_wia = HashWithIndifferentAccess.new { 1 + 2 } - assert_equal nil, hash_wia.default + assert_nil hash_wia.default end def test_should_use_default_value_for_unknown_key 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/blank_test.rb b/activesupport/test/core_ext/object/blank_test.rb index ab0676524e..7fd3fed042 100644 --- a/activesupport/test/core_ext/object/blank_test.rb +++ b/activesupport/test/core_ext/object/blank_test.rb @@ -15,7 +15,7 @@ class BlankTest < ActiveSupport::TestCase end BLANK = [ EmptyTrue.new, nil, false, "", " ", " \n\t \r ", " ", "\u00a0", [], {} ] - NOT = [ EmptyFalse.new, Object.new, true, 0, 1, "a", [nil], { nil => 0 } ] + NOT = [ EmptyFalse.new, Object.new, true, 0, 1, "a", [nil], { nil => 0 }, Time.now ] def test_blank BLANK.each { |v| assert_equal true, v.blank?, "#{v.inspect} should be blank" } @@ -28,7 +28,7 @@ class BlankTest < ActiveSupport::TestCase end def test_presence - BLANK.each { |v| assert_equal nil, v.presence, "#{v.inspect}.presence should return nil" } + BLANK.each { |v| assert_nil v.presence, "#{v.inspect}.presence should return nil" } NOT.each { |v| assert_equal v, v.presence, "#{v.inspect}.presence should return self" } end end diff --git a/activesupport/test/core_ext/object/deep_dup_test.rb b/activesupport/test/core_ext/object/deep_dup_test.rb index e335ec1b40..f247ee16de 100644 --- a/activesupport/test/core_ext/object/deep_dup_test.rb +++ b/activesupport/test/core_ext/object/deep_dup_test.rb @@ -6,7 +6,7 @@ class DeepDupTest < ActiveSupport::TestCase array = [1, [2, 3]] dup = array.deep_dup dup[1][2] = 4 - assert_equal nil, array[1][2] + assert_nil array[1][2] assert_equal 4, dup[1][2] end @@ -14,7 +14,7 @@ class DeepDupTest < ActiveSupport::TestCase hash = { a: { b: "b" } } dup = hash.deep_dup dup[:a][:c] = "c" - assert_equal nil, hash[:a][:c] + assert_nil hash[:a][:c] assert_equal "c", dup[:a][:c] end @@ -22,7 +22,7 @@ class DeepDupTest < ActiveSupport::TestCase array = [1, { a: 2, b: 3 } ] dup = array.deep_dup dup[1][:c] = 4 - assert_equal nil, array[1][:c] + assert_nil array[1][:c] assert_equal 4, dup[1][:c] end @@ -30,7 +30,7 @@ class DeepDupTest < ActiveSupport::TestCase hash = { a: [1, 2] } dup = hash.deep_dup dup[:a][2] = "c" - assert_equal nil, hash[:a][2] + assert_nil hash[:a][2] assert_equal "c", dup[:a][2] end diff --git a/activesupport/test/core_ext/object/duplicable_test.rb b/activesupport/test/core_ext/object/duplicable_test.rb index 677e32db1d..e6f3c8aef2 100644 --- a/activesupport/test/core_ext/object/duplicable_test.rb +++ b/activesupport/test/core_ext/object/duplicable_test.rb @@ -4,16 +4,23 @@ require "active_support/core_ext/object/duplicable" require "active_support/core_ext/numeric/time" class DuplicableTest < ActiveSupport::TestCase - RAISE_DUP = [nil, false, true, :symbol, 1, 2.3, method(:puts)] - ALLOW_DUP = ["1", Object.new, /foo/, [], {}, Time.now, Class.new, Module.new] - ALLOW_DUP << BigDecimal.new("4.56") + if RUBY_VERSION >= "2.4.1" + RAISE_DUP = [method(:puts), Complex(1), Rational(1)] + 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")] + end def test_duplicable rubinius_skip "* Method#dup is allowed at the moment on Rubinius\n" \ "* 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 98bcdda45b..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 @@ -339,7 +339,7 @@ class StringAccessTest < ActiveSupport::TestCase test "#at with Regex, returns the matching portion of the string" do assert_equal "lo", "hello".at(/lo/) - assert_equal nil, "hello".at(/nonexisting/) + assert_nil "hello".at(/nonexisting/) end test "#from with positive Integer, returns substring from the given position to the end" do @@ -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_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb index c11804c3dc..a399e36dc9 100644 --- a/activesupport/test/core_ext/time_ext_test.rb +++ b/activesupport/test/core_ext/time_ext_test.rb @@ -771,7 +771,7 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase assert_equal 1, Time.utc(2000) <=> Time.utc(1999, 12, 31, 23, 59, 59, 999).to_s assert_equal 0, Time.utc(2000) <=> Time.utc(2000, 1, 1, 0, 0, 0).to_s assert_equal(-1, Time.utc(2000) <=> Time.utc(2000, 1, 1, 0, 0, 1, 0).to_s) - assert_equal nil, Time.utc(2000) <=> "Invalid as Time" + assert_nil Time.utc(2000) <=> "Invalid as Time" end def test_at_with_datetime diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb index 620084f27d..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 @@ -1057,7 +1061,7 @@ class TimeWithZoneMethodsForTimeAndDateTimeTest < ActiveSupport::TestCase Time.zone = -9.hours assert_equal ActiveSupport::TimeZone["Alaska"], Time.zone Time.zone = nil - assert_equal nil, Time.zone + assert_nil Time.zone end def test_time_zone_getter_and_setter_with_zone_default_set diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb index f333a46d75..e38d4e83e5 100644 --- a/activesupport/test/dependencies_test.rb +++ b/activesupport/test/dependencies_test.rb @@ -121,7 +121,7 @@ class DependenciesTest < ActiveSupport::TestCase silence_warnings { require_dependency filename } assert_equal 2, $check_warnings_load_count - assert_equal nil, $checked_verbose, "After first load warnings should be left alone." + assert_nil $checked_verbose, "After first load warnings should be left alone." assert_includes ActiveSupport::Dependencies.loaded, expanded ActiveSupport::Dependencies.clear @@ -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 @@ -526,8 +530,8 @@ class DependenciesTest < ActiveSupport::TestCase def test_file_search with_loading "dependencies" do root = ActiveSupport::Dependencies.autoload_paths.first - assert_equal nil, ActiveSupport::Dependencies.search_for_file("service_three") - assert_equal nil, ActiveSupport::Dependencies.search_for_file("service_three.rb") + assert_nil ActiveSupport::Dependencies.search_for_file("service_three") + assert_nil ActiveSupport::Dependencies.search_for_file("service_three.rb") assert_equal root + "/service_one.rb", ActiveSupport::Dependencies.search_for_file("service_one") assert_equal root + "/service_one.rb", ActiveSupport::Dependencies.search_for_file("service_one.rb") end @@ -548,7 +552,6 @@ class DependenciesTest < ActiveSupport::TestCase assert_equal autoload + "/conflict.rb", ActiveSupport::Dependencies.search_for_file("conflict") end - end def test_custom_const_missing_should_work 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/descendants_tracker_test_cases.rb b/activesupport/test/descendants_tracker_test_cases.rb index 09c5ce1f07..cf349d53ee 100644 --- a/activesupport/test/descendants_tracker_test_cases.rb +++ b/activesupport/test/descendants_tracker_test_cases.rb @@ -40,7 +40,7 @@ module DescendantsTrackerTestCases end end - protected + private def assert_equal_sets(expected, actual) assert_equal Set.new(expected), Set.new(actual) 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/executor_test.rb b/activesupport/test/executor_test.rb index 03ec6020c3..7fefc066b3 100644 --- a/activesupport/test/executor_test.rb +++ b/activesupport/test/executor_test.rb @@ -105,7 +105,7 @@ class ExecutorTest < ActiveSupport::TestCase executor.wrap {} - assert_equal nil, supplied_state + assert_nil supplied_state end def test_exception_skips_uninvoked_hook 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.rb b/activesupport/test/json/encoding_test.rb index fa518104c2..6d8f7cfbd0 100644 --- a/activesupport/test/json/encoding_test.rb +++ b/activesupport/test/json/encoding_test.rb @@ -329,7 +329,7 @@ class TestJSONEncoding < ActiveSupport::TestCase end def test_nil_true_and_false_represented_as_themselves - assert_equal nil, nil.as_json + assert_nil nil.as_json assert_equal true, true.as_json assert_equal false, false.as_json end @@ -452,7 +452,7 @@ EXPECTED assert_equal '{"number":null}', NaNNumber.new.to_json end - protected + private def object_keys(json_object) json_object[1..-2].scan(/([^{}:,\s]+):/).flatten.sort 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 2ee4a1ce68..d80d340986 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 } @@ -231,7 +231,7 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase assert_equal 0, @chars.index("こに") assert_equal 2, @chars.index("ち") assert_equal 2, @chars.index("ち", -2) - assert_equal nil, @chars.index("ち", -1) + assert_nil @chars.index("ち", -1) assert_equal 3, @chars.index("わ") assert_equal 5, "ééxééx".mb_chars.index("x", 4) end @@ -390,11 +390,11 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase end def test_slice_should_take_character_offsets - assert_equal nil, "".mb_chars.slice(0) + assert_nil "".mb_chars.slice(0) assert_equal "こ", @chars.slice(0) assert_equal "わ", @chars.slice(3) - assert_equal nil, "".mb_chars.slice(-1..1) - assert_equal nil, "".mb_chars.slice(-1, 1) + assert_nil "".mb_chars.slice(-1..1) + assert_nil "".mb_chars.slice(-1, 1) assert_equal "", "".mb_chars.slice(0..10) assert_equal "にちわ", @chars.slice(1..3) assert_equal "にちわ", @chars.slice(1, 3) @@ -403,10 +403,10 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase assert_equal "", @chars.slice(4..10) assert_equal "に", @chars.slice(/に/u) assert_equal "にち", @chars.slice(/に./u) - assert_equal nil, @chars.slice(/unknown/u) + assert_nil @chars.slice(/unknown/u) assert_equal "にち", @chars.slice(/(にち)/u, 1) - assert_equal nil, @chars.slice(/(にち)/u, 2) - assert_equal nil, @chars.slice(7..6) + assert_nil @chars.slice(/(にち)/u, 2) + assert_nil @chars.slice(7..6) end def test_slice_bang_returns_sliced_out_substring @@ -414,7 +414,7 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase end def test_slice_bang_returns_nil_on_out_of_bound_arguments - assert_equal nil, @chars.mb_chars.slice!(9..10) + assert_nil @chars.mb_chars.slice!(9..10) end def test_slice_bang_removes_the_slice_from_the_receiver @@ -664,7 +664,6 @@ class MultibyteCharsExtrasTest < ActiveSupport::TestCase end def test_tidy_bytes_should_tidy_bytes - single_byte_cases = { "\x21" => "!", # Valid ASCII byte, low "\x41" => "A", # Valid ASCII byte, mid diff --git a/activesupport/test/multibyte_conformance_test.rb b/activesupport/test/multibyte_conformance_test.rb index bf004d7924..ef1a26135f 100644 --- a/activesupport/test/multibyte_conformance_test.rb +++ b/activesupport/test/multibyte_conformance_test.rb @@ -82,7 +82,7 @@ class MultibyteConformanceTest < ActiveSupport::TestCase end end - protected + private def each_line_of_norm_tests(&block) File.open(File.join(CACHE_DIR, UNIDATA_FILE), "r") do | f | until f.eof? diff --git a/activesupport/test/multibyte_grapheme_break_conformance_test.rb b/activesupport/test/multibyte_grapheme_break_conformance_test.rb index 04a7d290d9..b3328987ae 100644 --- a/activesupport/test/multibyte_grapheme_break_conformance_test.rb +++ b/activesupport/test/multibyte_grapheme_break_conformance_test.rb @@ -28,7 +28,7 @@ class MultibyteGraphemeBreakConformanceTest < ActiveSupport::TestCase end end - protected + private def each_line_of_break_tests(&block) lines = 0 max_test_lines = 0 # Don't limit below 21, because that's the header of the testfile diff --git a/activesupport/test/multibyte_normalization_conformance_test.rb b/activesupport/test/multibyte_normalization_conformance_test.rb index e013bd578f..ebc9f92d23 100644 --- a/activesupport/test/multibyte_normalization_conformance_test.rb +++ b/activesupport/test/multibyte_normalization_conformance_test.rb @@ -83,7 +83,7 @@ class MultibyteNormalizationConformanceTest < ActiveSupport::TestCase end end - protected + private def each_line_of_norm_tests(&block) lines = 0 max_test_lines = 0 # Don't limit below 38, because that's the header of the testfile 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/notifications_test.rb b/activesupport/test/notifications_test.rb index a6f0d82e8a..11f743519f 100644 --- a/activesupport/test/notifications_test.rb +++ b/activesupport/test/notifications_test.rb @@ -273,7 +273,7 @@ module Notifications assert !not_child.parent_of?(parent) end - protected + private def random_id @random_id ||= SecureRandom.hex(10) end 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/test_case_test.rb b/activesupport/test/test_case_test.rb index d769a8c145..af7fc44d66 100644 --- a/activesupport/test/test_case_test.rb +++ b/activesupport/test/test_case_test.rb @@ -257,7 +257,7 @@ class SetupAndTeardownTest < ActiveSupport::TestCase def teardown end - protected + private def reset_callback_record @called_back = [] @@ -282,7 +282,7 @@ class SubclassSetupAndTeardownTest < SetupAndTeardownTest assert_equal [:foo, :sentinel, :bar], self.class._teardown_callbacks.map(&:raw_filter) end - protected + private def bar @called_back << :bar 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/time_zone_test.rb b/activesupport/test/time_zone_test.rb index 914893ea10..4794b55742 100644 --- a/activesupport/test/time_zone_test.rb +++ b/activesupport/test/time_zone_test.rb @@ -416,7 +416,7 @@ class TimeZoneTest < ActiveSupport::TestCase def test_utc_offset_lazy_loaded_from_tzinfo_when_not_passed_in_to_initialize tzinfo = TZInfo::Timezone.get("America/New_York") zone = ActiveSupport::TimeZone.create(tzinfo.name, nil, tzinfo) - assert_equal nil, zone.instance_variable_get("@utc_offset") + assert_nil zone.instance_variable_get("@utc_offset") assert_equal(-18_000, zone.utc_offset) 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 bf501f6a64..ccb42f3273 100644 --- a/guides/Rakefile +++ b/guides/Rakefile @@ -1,10 +1,8 @@ namespace :guides do - desc 'Generate guides (for authors), use ONLY=foo to process just "foo.md"' task generate: "generate:html" namespace :generate do - desc "Generate HTML guides" task :html do ENV["WARNINGS"] = "1" # authors can't disable this @@ -13,11 +11,12 @@ 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 - unless `kindlerb -v 2> /dev/null` =~ /kindlerb 1.0.1/ - abort "Please `gem install kindlerb` and make sure you have `kindlegen` in your PATH" + require "kindlerb" + unless Kindlerb.kindlegen_available? + abort "Please run `setupkindlerb` to install kindlegen" end unless `convert` =~ /convert/ - abort "Please install ImageMagick`" + abort "Please install ImageMagick" end ENV["KINDLE"] = "1" Rake::Task["guides:generate:html"].invoke @@ -32,7 +31,7 @@ namespace :guides do desc "Show help" task :help do - puts <<-help + puts <<HELP Guides are taken from the source directory, and the result goes into the output directory. Assets are stored under files, and copied to output/files as @@ -45,8 +44,9 @@ All of these processes are handled via rake tasks, here's a full list of them: #{%x[rake -T]} Some arguments may be passed via environment variables: - WARNINGS=1 - Internal links (anchors) are checked, also detects duplicated IDs. + RAILS_VERSION=tag + If guides are being generated for a specific Rails version set the Git tag + here, otherwise the current SHA1 is going to be used to generate edge guides. ALL=1 Force generation of all guides. @@ -64,15 +64,12 @@ Some arguments may be passed via environment variables: Use it when you want to generate translated guides in source/<GUIDES_LANGUAGE> folder (such as source/es) - EDGE=1 - Indicate generated guides should be marked as edge. - Examples: - $ rake guides:generate ALL=1 - $ rake guides:generate EDGE=1 - $ rake guides:generate:kindle EDGE=1 + $ rake guides:generate ALL=1 RAILS_VERSION=v5.1.0 + $ rake guides:generate ONLY=migrations + $ rake guides:generate:kindle $ rake guides:generate GUIDES_LANGUAGE=es - help +HELP end 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.rb b/guides/rails_guides.rb index 557d23f78c..0f611c8f2b 100644 --- a/guides/rails_guides.rb +++ b/guides/rails_guides.rb @@ -1,17 +1,27 @@ -pwd = File.dirname(__FILE__) -$:.unshift pwd +$:.unshift __dir__ -begin - # Guides generation in the Rails repo. - as_lib = File.join(pwd, "../activesupport/lib") - ap_lib = File.join(pwd, "../actionpack/lib") +as_lib = File.expand_path("../activesupport/lib", __dir__) +ap_lib = File.expand_path("../actionpack/lib", __dir__) +av_lib = File.expand_path("../actionview/lib", __dir__) - $:.unshift as_lib if File.directory?(as_lib) - $:.unshift ap_lib if File.directory?(ap_lib) -rescue LoadError - # Guides generation from gems. - gem "actionpack", ">= 3.0" -end +$:.unshift as_lib if File.directory?(as_lib) +$:.unshift ap_lib if File.directory?(ap_lib) +$:.unshift av_lib if File.directory?(av_lib) require "rails_guides/generator" -RailsGuides::Generator.new.generate +require "active_support/core_ext/object/blank" + +env_value = ->(name) { ENV[name].presence } +env_flag = ->(name) { "1" == env_value[name] } + +version = env_value["RAILS_VERSION"] +edge = `git rev-parse HEAD`.strip unless version + +RailsGuides::Generator.new( + edge: edge, + version: version, + all: env_flag["ALL"], + only: env_value["ONLY"], + kindle: env_flag["KINDLE"], + language: env_value["GUIDES_LANGUAGE"] +).generate diff --git a/guides/rails_guides/generator.rb b/guides/rails_guides/generator.rb index a818ca9d72..28164a3cb4 100644 --- a/guides/rails_guides/generator.rb +++ b/guides/rails_guides/generator.rb @@ -1,54 +1,3 @@ -# --------------------------------------------------------------------------- -# -# This script generates the guides. It can be invoked via the -# guides:generate rake task within the guides directory. -# -# Guides are taken from the source directory, and the resulting HTML goes into the -# output directory. Assets are stored under files, and copied to output/files as -# part of the generation process. -# -# Some arguments may be passed via environment variables: -# -# WARNINGS -# If you are writing a guide, please work always with WARNINGS=1. Users can -# generate the guides, and thus this flag is off by default. -# -# Internal links (anchors) are checked. If a reference is broken levenshtein -# distance is used to suggest an existing one. This is useful since IDs are -# generated by Markdown from headers and thus edits alter them. -# -# Also detects duplicated IDs. They happen if there are headers with the same -# text. Please do resolve them, if any, so guides are valid XHTML. -# -# ALL -# Set to "1" to force the generation of all guides. -# -# ONLY -# Use ONLY if you want to generate only one or a set of guides. Prefixes are -# enough: -# -# # generates only association_basics.html -# ONLY=assoc rake guides:generate -# -# Separate many using commas: -# -# # generates only association_basics.html and command_line.html -# ONLY=assoc,command rake guides:generate -# -# Note that if you are working on a guide generation will by default process -# only that one, so ONLY is rarely used nowadays. -# -# GUIDES_LANGUAGE -# Use GUIDES_LANGUAGE when you want to generate translated guides in -# <tt>source/<GUIDES_LANGUAGE></tt> folder (such as <tt>source/es</tt>). -# Ignore it when generating English guides. -# -# EDGE -# Set to "1" to indicate generated guides should be marked as edge. This -# inserts a badge and changes the preamble of the home page. -# -# --------------------------------------------------------------------------- - require "set" require "fileutils" @@ -64,46 +13,37 @@ require "rails_guides/levenshtein" module RailsGuides class Generator - attr_reader :guides_dir, :source_dir, :output_dir, :edge, :warnings, :all - GUIDES_RE = /\.(?:erb|md)\z/ - def initialize(output = nil) - set_flags_from_environment + def initialize(edge:, version:, all:, only:, kindle:, language:) + @edge = edge + @version = version + @all = all + @only = only + @kindle = kindle + @language = language - if kindle? + if @kindle check_for_kindlegen register_kindle_mime_types end - initialize_dirs(output) + initialize_dirs create_output_dir_if_needed - end - - def set_flags_from_environment - @edge = ENV["EDGE"] == "1" - @warnings = ENV["WARNINGS"] == "1" - @all = ENV["ALL"] == "1" - @kindle = ENV["KINDLE"] == "1" - @version = ENV["RAILS_VERSION"] || "local" - @lang = ENV["GUIDES_LANGUAGE"] - end - - def register_kindle_mime_types - Mime::Type.register_alias("application/xml", :opf, %w(opf)) - Mime::Type.register_alias("application/xml", :ncx, %w(ncx)) + initialize_markdown_renderer end def generate generate_guides copy_assets - generate_mobi if kindle? + generate_mobi if @kindle end private - def kindle? - @kindle + def register_kindle_mime_types + Mime::Type.register_alias("application/xml", :opf, %w(opf)) + Mime::Type.register_alias("application/xml", :ncx, %w(ncx)) end def check_for_kindlegen @@ -114,29 +54,35 @@ module RailsGuides def generate_mobi require "rails_guides/kindle" - out = "#{output_dir}/kindlegen.out" - Kindle.generate(output_dir, mobi, out) + out = "#{@output_dir}/kindlegen.out" + Kindle.generate(@output_dir, mobi, out) puts "(kindlegen log at #{out})." end def mobi - "ruby_on_rails_guides_#@version%s.mobi" % (@lang.present? ? ".#@lang" : "") + mobi = "ruby_on_rails_guides_#{@version || @edge[0, 7]}" + mobi += ".#{@language}" if @language + mobi += ".mobi" end - def initialize_dirs(output) - @guides_dir = File.join(File.dirname(__FILE__), "..") - @source_dir = "#@guides_dir/source/#@lang" - @output_dir = if output - output - elsif kindle? - "#@guides_dir/output/kindle/#@lang" - else - "#@guides_dir/output/#@lang" - end.sub(%r</$>, "") + def initialize_dirs + @guides_dir = File.expand_path("..", __dir__) + + @source_dir = "#{@guides_dir}/source" + @source_dir += "/#{@language}" if @language + + @output_dir = "#{@guides_dir}/output" + @output_dir += "/kindle" if @kindle + @source_dir += "/#{@language}" if @language end def create_output_dir_if_needed - FileUtils.mkdir_p(output_dir) + FileUtils.mkdir_p(@output_dir) + end + + def initialize_markdown_renderer + Markdown::Renderer.edge = @edge + Markdown::Renderer.version = @version end def generate_guides @@ -147,27 +93,27 @@ module RailsGuides end def guides_to_generate - guides = Dir.entries(source_dir).grep(GUIDES_RE) + guides = Dir.entries(@source_dir).grep(GUIDES_RE) - if kindle? - Dir.entries("#{source_dir}/kindle").grep(GUIDES_RE).map do |entry| + if @kindle + Dir.entries("#{@source_dir}/kindle").grep(GUIDES_RE).map do |entry| next if entry == "KINDLE.md" guides << "kindle/#{entry}" end end - ENV.key?("ONLY") ? select_only(guides) : guides + @only ? select_only(guides) : guides end def select_only(guides) - prefixes = ENV["ONLY"].split(",").map(&:strip) + prefixes = @only.split(",").map(&:strip) guides.select do |guide| - guide.start_with?("kindle".freeze, *prefixes) + guide.start_with?("kindle", *prefixes) end end def copy_assets - FileUtils.cp_r(Dir.glob("#{guides_dir}/assets/*"), output_dir) + FileUtils.cp_r(Dir.glob("#{@guides_dir}/assets/*"), @output_dir) end def output_file_for(guide) @@ -179,22 +125,28 @@ module RailsGuides end def output_path_for(output_file) - File.join(output_dir, File.basename(output_file)) + File.join(@output_dir, File.basename(output_file)) end def generate?(source_file, output_file) - fin = File.join(source_dir, source_file) + fin = File.join(@source_dir, source_file) fout = output_path_for(output_file) - all || !File.exist?(fout) || File.mtime(fout) < File.mtime(fin) + @all || !File.exist?(fout) || File.mtime(fout) < File.mtime(fin) end def generate_guide(guide, output_file) output_path = output_path_for(output_file) puts "Generating #{guide} as #{output_file}" - layout = kindle? ? "kindle/layout" : "layout" + layout = @kindle ? "kindle/layout" : "layout" File.open(output_path, "w") do |f| - view = ActionView::Base.new(source_dir, edge: @edge, version: @version, mobi: "kindle/#{mobi}", lang: @lang) + view = ActionView::Base.new( + @source_dir, + edge: @edge, + version: @version, + mobi: "kindle/#{mobi}", + language: @language + ) view.extend(Helpers) if guide =~ /\.(\w+)\.erb$/ @@ -202,10 +154,15 @@ module RailsGuides # Passing a template handler in the template name is deprecated. So pass the file name without the extension. result = view.render(layout: layout, formats: [$1], file: $`) else - body = File.read(File.join(source_dir, guide)) - result = RailsGuides::Markdown.new(view, layout).render(body) - - warn_about_broken_links(result) if @warnings + body = File.read("#{@source_dir}/#{guide}") + result = RailsGuides::Markdown.new( + view: view, + layout: layout, + edge: @edge, + version: @version + ).render(body) + + warn_about_broken_links(result) end f.write(result) @@ -231,7 +188,7 @@ module RailsGuides # Footnotes. anchors += Set.new(html.scan(/<p\s+class="footnote"\s+id="([^"]+)/).flatten) anchors += Set.new(html.scan(/<sup\s+class="footnote"\s+id="([^"]+)/).flatten) - return anchors + anchors end def check_fragment_identifiers(html, anchors) diff --git a/guides/rails_guides/kindle.rb b/guides/rails_guides/kindle.rb index 4b73ae9518..9536d0bd3b 100644 --- a/guides/rails_guides/kindle.rb +++ b/guides/rails_guides/kindle.rb @@ -1,9 +1,6 @@ #!/usr/bin/env ruby -unless `which kindlerb` - abort "Please gem install kindlerb" -end - +require "kindlerb" require "nokogiri" require "fileutils" require "yaml" @@ -28,10 +25,9 @@ module Kindle generate_document_metadata(mobi_outfile) puts "Creating MOBI document with kindlegen. This may take a while." - cmd = "kindlerb . > #{File.absolute_path logfile} 2>&1" - puts cmd - system(cmd) - puts "MOBI document generated at #{File.expand_path(mobi_outfile, output_dir)}" + if Kindlerb.run(output_dir) + puts "MOBI document generated at #{File.expand_path(mobi_outfile, output_dir)}" + end end end diff --git a/guides/rails_guides/markdown.rb b/guides/rails_guides/markdown.rb index 009e5aff99..bf2cc82c7c 100644 --- a/guides/rails_guides/markdown.rb +++ b/guides/rails_guides/markdown.rb @@ -4,12 +4,14 @@ require "rails_guides/markdown/renderer" module RailsGuides class Markdown - def initialize(view, layout) - @view = view - @layout = layout + def initialize(view:, layout:, edge:, version:) + @view = view + @layout = layout + @edge = edge + @version = version @index_counter = Hash.new(0) - @raw_header = "" - @node_ids = {} + @raw_header = "" + @node_ids = {} end def render(body) @@ -60,7 +62,8 @@ module RailsGuides autolink: true, strikethrough: true, superscript: true, - tables: true) + tables: true + ) end def extract_raw_header_and_body diff --git a/guides/rails_guides/markdown/renderer.rb b/guides/rails_guides/markdown/renderer.rb index deab741023..20cbd568c9 100644 --- a/guides/rails_guides/markdown/renderer.rb +++ b/guides/rails_guides/markdown/renderer.rb @@ -1,9 +1,7 @@ module RailsGuides class Markdown class Renderer < Redcarpet::Render::HTML - def initialize(options = {}) - super - end + cattr_accessor :edge, :version def block_code(code, language) <<-HTML @@ -15,6 +13,16 @@ module RailsGuides HTML end + def link(url, title, content) + if url.start_with?("http://api.rubyonrails.org") + %(<a href="#{api_link(url)}">#{content}</a>) + elsif title + %(<a href="#{url}" title="#{title}">#{content}</a>) + else + %(<a href="#{url}">#{content}</a>) + end + end + def header(text, header_level) # Always increase the heading level by 1, so we can use h1, h2 heading in the document header_level += 1 @@ -23,7 +31,9 @@ HTML end def paragraph(text) - if text =~ /^(TIP|IMPORTANT|CAUTION|WARNING|NOTE|INFO|TODO)[.:]/ + if text =~ %r{^NOTE:\s+Defined\s+in\s+<code>(.*?)</code>\.?$} + %(<div class="note"><p>Defined in <code><a href="#{github_file_url($1)}">#{$1}</a></code>.</p></div>) + elsif text =~ /^(TIP|IMPORTANT|CAUTION|WARNING|NOTE|INFO|TODO)[.:]/ convert_notes(text) elsif text.include?("DO NOT READ THIS FILE ON GITHUB") elsif text =~ /^\[<sup>(\d+)\]:<\/sup> (.+)$/ @@ -79,6 +89,33 @@ HTML %(<div class="#{css_class}"><p>#{$2.strip}</p></div>) end end + + def github_file_url(file_path) + tree = version || edge + + root = file_path[%r{(.+)/}, 1] + path = case root + when "abstract_controller", "action_controller", "action_dispatch" + "actionpack/lib/#{file_path}" + when /\A(action|active)_/ + "#{root.sub("_", "")}/lib/#{file_path}" + else + file_path + end + + + "https://github.com/rails/rails/tree/#{tree}/#{path}" + end + + def api_link(url) + if url =~ %r{http://api\.rubyonrails\.org/v\d+\.} + url + elsif edge + url.sub("api", "edgeapi") + else + url.sub(/(?<=\.org)/, "/#{version}") + end + end end end end diff --git a/guides/source/5_0_release_notes.md b/guides/source/5_0_release_notes.md index 39753cbd6f..e1b3b0a42e 100644 --- a/guides/source/5_0_release_notes.md +++ b/guides/source/5_0_release_notes.md @@ -150,7 +150,7 @@ The type of an attribute is given the opportunity to change how dirty tracking is performed. See its -[documentation](http://api.rubyonrails.org/classes/ActiveRecord/Attributes/ClassMethods.html) +[documentation](http://api.rubyonrails.org/v5.0.1/classes/ActiveRecord/Attributes/ClassMethods.html) for a detailed write up. @@ -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/_welcome.html.erb b/guides/source/_welcome.html.erb index f50bcddbe7..5bd1ea4d22 100644 --- a/guides/source/_welcome.html.erb +++ b/guides/source/_welcome.html.erb @@ -1,8 +1,8 @@ -<h2>Ruby on Rails Guides (<%= @edge ? @version[0, 7] : @version %>)</h2> +<h2>Ruby on Rails Guides (<%= @edge ? @edge[0, 7] : @version %>)</h2> <% if @edge %> <p> - These are <b>Edge Guides</b>, based on the current <a href="https://github.com/rails/rails/tree/<%= @version %>">master</a> branch. + These are <b>Edge Guides</b>, based on <a href="https://github.com/rails/rails/tree/<%= @edge %>">master@<%= @edge[0, 7] %></a>. </p> <p> If you are looking for the ones for the stable version, please check diff --git a/guides/source/action_cable_overview.md b/guides/source/action_cable_overview.md index 3716aa0ecb..e929945dd0 100644 --- a/guides/source/action_cable_overview.md +++ b/guides/source/action_cable_overview.md @@ -62,7 +62,7 @@ module ApplicationCable self.current_user = find_verified_user end - protected + private def find_verified_user if current_user = User.find_by(id: cookies.signed[:user_id]) current_user @@ -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 2a1c960887..666d987f8c 100644 --- a/guides/source/active_record_callbacks.md +++ b/guides/source/active_record_callbacks.md @@ -36,7 +36,7 @@ class User < ApplicationRecord before_validation :ensure_login_has_a_value - protected + private def ensure_login_has_a_value if login.nil? self.login = email unless email.blank? @@ -66,7 +66,7 @@ class User < ApplicationRecord # :on takes an array as well after_validation :set_location, on: [ :create, :update ] - protected + private def normalize_name self.name = name.downcase.titleize end @@ -77,7 +77,7 @@ class User < ApplicationRecord end ``` -It is considered good practice to declare callback methods as protected or private. If left public, they can be called from outside of the model and violate the principle of object encapsulation. +It is considered good practice to declare callback methods as private. If left public, they can be called from outside of the model and violate the principle of object encapsulation. Available Callbacks ------------------- @@ -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/configuring.md b/guides/source/configuring.md index b0334bfe4a..251b038ec9 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -108,7 +108,7 @@ application. Accepts a valid week day symbol (e.g. `:monday`). you don't want shown in the logs, such as passwords or credit card numbers. By default, Rails filters out passwords by adding `Rails.application.config.filter_parameters += [:password]` in `config/initializers/filter_parameter_logging.rb`. Parameters filter works by partial matching regular expression. -* `config.force_ssl` forces all requests to be served over HTTPS by using the `ActionDispatch::SSL` middleware, and sets `config.action_mailer.default_url_options` to be `{ protocol: 'https' }`. This can be configured by setting `config.ssl_options` - see the [ActionDispatch::SSL documentation](http://edgeapi.rubyonrails.org/classes/ActionDispatch/SSL.html) for details. +* `config.force_ssl` forces all requests to be served over HTTPS by using the `ActionDispatch::SSL` middleware, and sets `config.action_mailer.default_url_options` to be `{ protocol: 'https' }`. This can be configured by setting `config.ssl_options` - see the [ActionDispatch::SSL documentation](http://api.rubyonrails.org/classes/ActionDispatch/SSL.html) for details. * `config.log_formatter` defines the formatter of the Rails logger. This option defaults to an instance of `ActiveSupport::Logger::SimpleFormatter` for all modes. If you are setting a value for `config.logger` you must manually pass the value of your formatter to your logger before it is wrapped in an `ActiveSupport::TaggedLogging` instance, Rails will not do it for you. @@ -368,6 +368,11 @@ All these configuration options are delegated to the `I18n` library. has_many relationships to be displayed with an index as well as the error. Defaults to `false`. +* `config.active_record.use_schema_cache_dump` enables users to get schema cache information + from `db/schema_cache.yml` (generated by `bin/rails db:schema:cache:dump`), instead of + having to send a query to the database to get this information. + Defaults to `true`. + The MySQL adapter adds one additional configuration option: * `ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans` controls whether Active Record will consider all `tinyint(1)` columns as booleans. Defaults to `true`. @@ -626,8 +631,6 @@ There are a few configuration options available in Active Support: * `config.active_support.time_precision` sets the precision of JSON encoded time values. Defaults to `3`. -* `ActiveSupport.halt_callback_chains_on_return_false` specifies whether Active Record and Active Model callback chains can be halted by returning `false` in a 'before' callback. When set to `false`, callback chains are halted only when explicitly done so with `throw(:abort)`. When set to `true`, callback chains are halted when a callback returns `false` (the previous behavior before Rails 5) and a deprecation warning is given. Defaults to `true` during the deprecation period. New Rails 5 apps generate an initializer file called `new_framework_defaults.rb` which sets the value to `false`. This file is *not* added when running `rails app:update`, so returning `false` will still work on older apps ported to Rails 5 and display a deprecation warning to prompt users to update their code. - * `ActiveSupport::Logger.silencer` is set to `false` to disable the ability to silence logging in a block. The default is `true`. * `ActiveSupport::Cache::Store.logger` specifies the logger to use within cache store operations. 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/development_dependencies_install.md b/guides/source/development_dependencies_install.md index 20cd34c182..16c7e782bc 100644 --- a/guides/source/development_dependencies_install.md +++ b/guides/source/development_dependencies_install.md @@ -162,6 +162,10 @@ $ cd actionpack $ bundle exec ruby -Itest path/to/test.rb -n test_name ``` +### Railties Setup + +Some Railties tests depend on a JavaScript runtime environment, such as having [Node.js](https://nodejs.org/) installed. + ### Active Record Setup Active Record's test suite runs three times: once for SQLite3, once for MySQL, and once for PostgreSQL. We are going to see now how to set up the environment for them. diff --git a/guides/source/generators.md b/guides/source/generators.md index 32bbdc554a..d0b6cef3fd 100644 --- a/guides/source/generators.md +++ b/guides/source/generators.md @@ -208,7 +208,15 @@ $ bin/rails generate scaffold User name:string Looking at this output, it's easy to understand how generators work in Rails 3.0 and above. The scaffold generator doesn't actually generate anything, it just invokes others to do the work. This allows us to add/replace/remove any of those invocations. For instance, the scaffold generator invokes the scaffold_controller generator, which invokes erb, test_unit and helper generators. Since each generator has a single responsibility, they are easy to reuse, avoiding code duplication. -Our first customization on the workflow will be to stop generating stylesheet, JavaScript and test fixture files for scaffolds. We can achieve that by changing our configuration to the following: +If we want to avoid generating the default `app/assets/stylesheets/scaffolds.scss` file when scaffolding a new resource we can disable `scaffold_stylesheet`: + +```ruby + config.generators do |g| + g.scaffold_stylesheet false + end +``` + +The next customization on the workflow will be to stop generating stylesheet, JavaScript and test fixture files for scaffolds altogether. We can achieve that by changing our configuration to the following: ```ruby config.generators do |g| @@ -451,6 +459,26 @@ $ rails new thud -m https://gist.github.com/radar/722911/raw/ Whilst the final section of this guide doesn't cover how to generate the most awesome template known to man, it will take you through the methods available at your disposal so that you can develop it yourself. These same methods are also available for generators. +Adding Command Line Arguments +----------------------------- +Rails generators can be easily modified to accept custom command line arguments. This functionality comes from [Thor](http://www.rubydoc.info/github/erikhuda/thor/master/Thor/Base/ClassMethods#class_option-instance_method): + +``` +class_option :scope, type: :string, default: 'read_products' +``` + +Now our generator can be invoked as follows: + +```bash +rails generate initializer --scope write_products +``` + +The command line arguments are accessed through the `options` method inside the generator class. e.g: + +```ruby +@scope = options['scope'] +``` + Generator methods ----------------- diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index 6ec5106bb3..8a451ab793 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -474,7 +474,7 @@ one here because the `ArticlesController` inherits from `ApplicationController`. The next part of the message contains `request.formats` which specifies the format of template to be served in response. It is set to `text/html` as we requested this page via browser, so Rails is looking for an HTML template. -`request.variants` specifies what kind of physical devices would be served by +`request.variant` specifies what kind of physical devices would be served by the response and helps Rails determine which template to use in the response. It is empty because no information has been provided. @@ -827,7 +827,7 @@ NOTE: A frequent practice is to place the standard CRUD actions in each controller in the following order: `index`, `show`, `new`, `edit`, `create`, `update` and `destroy`. You may use any order you choose, but keep in mind that these are public methods; as mentioned earlier in this guide, they must be placed -before any private or protected method in the controller in order to work. +before declaring `private` visibility in the controller. Given that, let's add the `show` action, as follows: 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 57ed35d0d8..3ea156c6fe 100644 --- a/guides/source/initialization.md +++ b/guides/source/initialization.md @@ -74,7 +74,7 @@ This file is as follows: ```ruby #!/usr/bin/env ruby -APP_PATH = File.expand_path('../../config/application', __FILE__) +APP_PATH = File.expand_path('../config/application', __dir__) require_relative '../config/boot' require 'rails/commands' ``` @@ -86,7 +86,7 @@ The `APP_PATH` constant will be used later in `rails/commands`. The `config/boot `config/boot.rb` contains: ```ruby -ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) require 'bundler/setup' # Set up gems listed in the Gemfile. ``` @@ -131,7 +131,7 @@ Once `config/boot.rb` has finished, the next file that is required is `ARGV` array simply contains `server` which will be passed over: ```ruby -ARGV << '--help' if ARGV.empty? +require "rails/command" aliases = { "g" => "generate", @@ -146,33 +146,37 @@ aliases = { command = ARGV.shift command = aliases[command] || command -require 'rails/commands/commands_tasks' - -Rails::CommandsTasks.new(ARGV).run_command!(command) +Rails::Command.invoke command, ARGV ``` -TIP: As you can see, an empty ARGV list will make Rails show the help -snippet. - If we had used `s` rather than `server`, Rails would have used the `aliases` defined here to find the matching command. -### `rails/commands/commands_tasks.rb` +### `rails/command.rb` -When one types a valid Rails command, `run_command!` a method of the same name -is called. If Rails doesn't recognize the command, it tries to run a Rake task -of the same name. +When one types a Rails command, `invoke` tries to lookup a command for the given +namespace and executing the command if found. -```ruby -COMMAND_WHITELIST = %w(plugin generate destroy console server dbconsole application runner new version help) +If Rails doesn't recognize the command, it hands the reins over to Rake +to run a task of the same name. -def run_command!(command) - command = parse_command(command) +As shown, `Rails::Command` displays the help output automatically if the `args` +are empty. - if COMMAND_WHITELIST.include?(command) - send(command) - else - run_rake_task(command) +```ruby +module Rails::Command + class << self + def invoke(namespace, args = [], **config) + namespace = namespace.to_s + namespace = "help" if namespace.blank? || HELP_MAPPINGS.include?(namespace) + namespace = "version" if %w( -v --version ).include? namespace + + if command = find_by_namespace(namespace) + command.perform(namespace, args, config) + else + find_by_namespace("rake").perform(namespace, args, config) + end + end end end ``` @@ -180,53 +184,39 @@ end With the `server` command, Rails will further run the following code: ```ruby -def set_application_directory! - Dir.chdir(File.expand_path('../../', APP_PATH)) unless File.exist?(File.expand_path("config.ru")) -end - -def server - set_application_directory! - require_command!("server") - - Rails::Server.new.tap do |server| - # We need to require application after the server sets environment, - # otherwise the --environment option given to the server won't propagate. - require APP_PATH - Dir.chdir(Rails.application.root) - server.start +module Rails + module Command + class ServerCommand < Base # :nodoc: + def perform + set_application_directory! + + Rails::Server.new.tap do |server| + # Require application after server sets environment to propagate + # the --environment option. + require APP_PATH + Dir.chdir(Rails.application.root) + server.start + end + end + end end end - -def require_command!(command) - require "rails/commands/#{command}" -end ``` This file will change into the Rails root directory (a path two directories up from `APP_PATH` which points at `config/application.rb`), but only if the -`config.ru` file isn't found. This then requires `rails/commands/server` which -sets up the `Rails::Server` class. - -```ruby -require 'fileutils' -require 'optparse' -require 'action_dispatch' -require 'rails' - -module Rails - class Server < ::Rack::Server -``` - -`fileutils` and `optparse` are standard Ruby libraries which provide helper functions for working with files and parsing options. +`config.ru` file isn't found. This then starts up the `Rails::Server` class. ### `actionpack/lib/action_dispatch.rb` Action Dispatch is the routing component of the Rails framework. It adds functionality like routing, session, and common middlewares. -### `rails/commands/server.rb` +### `rails/commands/server/server_command.rb` -The `Rails::Server` class is defined in this file by inheriting from `Rack::Server`. When `Rails::Server.new` is called, this calls the `initialize` method in `rails/commands/server.rb`: +The `Rails::Server` class is defined in this file by inheriting from +`Rack::Server`. When `Rails::Server.new` is called, this calls the `initialize` +method in `rails/commands/server/server_command.rb`: ```ruby def initialize(*) @@ -252,7 +242,10 @@ end In this case, `options` will be `nil` so nothing happens in this method. -After `super` has finished in `Rack::Server`, we jump back to `rails/commands/server.rb`. At this point, `set_environment` is called within the context of the `Rails::Server` object and this method doesn't appear to do much at first glance: +After `super` has finished in `Rack::Server`, we jump back to +`rails/commands/server/server_command.rb`. At this point, `set_environment` +is called within the context of the `Rails::Server` object and this method +doesn't appear to do much at first glance: ```ruby def set_environment @@ -289,17 +282,15 @@ With the `default_options` set to this: ```ruby def default_options - environment = ENV['RACK_ENV'] || 'development' - default_host = environment == 'development' ? 'localhost' : '0.0.0.0' - - { - :environment => environment, - :pid => nil, - :Port => 9292, - :Host => default_host, - :AccessLog => [], - :config => "config.ru" - } + 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) end ``` @@ -311,22 +302,25 @@ def opt_parser end ``` -The class **is** defined in `Rack::Server`, but is overwritten in `Rails::Server` to take different arguments. Its `parse!` method begins like this: +The class **is** defined in `Rack::Server`, but is overwritten in +`Rails::Server` to take different arguments. Its `parse!` method looks +like this: ```ruby def parse!(args) args, options = args.dup, {} - opt_parser = OptionParser.new do |opts| - opts.banner = "Usage: rails server [puma, thin, etc] [options]" - opts.on("-p", "--port=port", Integer, - "Runs Rails on the specified port.", "Default: 3000") { |v| options[:Port] = v } - ... + option_parser(options).parse! args + + options[:log_stdout] = options[:daemonize].blank? && (options[:environment] || Rails.env) == "development" + options[:server] = args.shift + options +end ``` This method will set up keys for the `options` which Rails will then be able to use to determine how its server should run. After `initialize` -has finished, we jump back into `rails/server` where `APP_PATH` (which was +has finished, we jump back into the server command where `APP_PATH` (which was set earlier) is required. ### `config/application` @@ -345,6 +339,7 @@ def start print_boot_information trap(:INT) { exit } create_tmp_directories + setup_dev_caching log_to_stdout if options[:log_stdout] super @@ -352,7 +347,6 @@ def start end private - def print_boot_information ... puts "=> Run `rails server -h` for more startup options" @@ -364,21 +358,30 @@ private end end + def setup_dev_caching + if options[:environment] == "development" + Rails::DevCaching.enable_by_argument(options[:caching]) + end + end + def log_to_stdout wrapped_app # touch the app so the logger is set up - - console = ActiveSupport::Logger.new($stdout) + + console = ActiveSupport::Logger.new(STDOUT) console.formatter = Rails.logger.formatter console.level = Rails.logger.level - - Rails.logger.extend(ActiveSupport::Logger.broadcast(console)) + + unless ActiveSupport::Logger.logger_outputs_to?(Rails.logger, STDOUT) + Rails.logger.extend(ActiveSupport::Logger.broadcast(console)) + end end ``` This is where the first output of the Rails initialization happens. This method creates a trap for `INT` signals, so if you `CTRL-C` the server, it will exit the process. As we can see from the code here, it will create the `tmp/cache`, -`tmp/pids`, and `tmp/sockets` directories. It then calls `wrapped_app` which is +`tmp/pids`, and `tmp/sockets` directories. It then enables caching in development +if `rails server` is called with `--dev-caching`. Finally, it calls `wrapped_app` which is responsible for creating the Rack app, before creating and assigning an instance of `ActiveSupport::Logger`. @@ -538,7 +541,7 @@ require "rails" sprockets/railtie ).each do |railtie| begin - require "#{railtie}" + require railtie rescue LoadError end end diff --git a/guides/source/kindle/rails_guides.opf.erb b/guides/source/kindle/rails_guides.opf.erb index 547abcbc19..63eeb007d7 100644 --- a/guides/source/kindle/rails_guides.opf.erb +++ b/guides/source/kindle/rails_guides.opf.erb @@ -5,7 +5,7 @@ <meta name="cover" content="cover" /> <dc-metadata xmlns:dc="http://purl.org/dc/elements/1.1/"> - <dc:title>Ruby on Rails Guides (<%= @version %>)</dc:title> + <dc:title>Ruby on Rails Guides (<%= @version || "master@#{@edge[0, 7]}" %>)</dc:title> <dc:language>en-us</dc:language> <dc:creator>Ruby on Rails</dc:creator> diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md index c8702f54fc..48bb3147f3 100644 --- a/guides/source/layouts_and_rendering.md +++ b/guides/source/layouts_and_rendering.md @@ -411,6 +411,8 @@ render formats: :xml render formats: [:json, :xml] ``` +If a template with the specified format does not exist an `ActionView::MissingTemplate` error is raised. + #### Finding Layouts To find the current layout, Rails first looks for a file in `app/views/layouts` with the same base name as the controller. For example, rendering actions from the `PhotosController` class will use `app/views/layouts/photos.html.erb` (or `app/views/layouts/photos.builder`). If there is no such controller-specific layout, Rails will use `app/views/layouts/application.html.erb` or `app/views/layouts/application.builder`. If there is no `.erb` layout, Rails will use a `.builder` layout if one exists. Rails also provides several ways to more precisely assign specific layouts to individual controllers and actions. @@ -1155,7 +1157,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/routing.md b/guides/source/routing.md index 937e313663..86492a9332 100644 --- a/guides/source/routing.md +++ b/guides/source/routing.md @@ -603,6 +603,14 @@ get 'photos/:id', to: 'photos#show', defaults: { format: 'jpg' } Rails would match `photos/12` to the `show` action of `PhotosController`, and set `params[:format]` to `"jpg"`. +You can also use `defaults` in a block format to define the defaults for multiple items: + +```ruby +defaults format: :json do + resources :photos +end +``` + NOTE: You cannot override defaults via query parameters - this is for security reasons. The only defaults that can be overridden are dynamic segments via substitution in the URL path. ### Naming Routes diff --git a/guides/source/ruby_on_rails_guides_guidelines.md b/guides/source/ruby_on_rails_guides_guidelines.md index 50866350f8..de63e193f4 100644 --- a/guides/source/ruby_on_rails_guides_guidelines.md +++ b/guides/source/ruby_on_rails_guides_guidelines.md @@ -50,6 +50,48 @@ Use the same inline formatting as regular text: ##### The `:content_type` Option ``` +Linking to the API +------------------ + +Links to the API (`api.rubyonrails.org`) are processed by the guides generator in the following manner: + +Links that include a release tag are left untouched. For example + +``` +http://api.rubyonrails.org/v5.0.1/classes/ActiveRecord/Attributes/ClassMethods.html +``` + +is not modified. + +Please use these in release notes, since they should point to the corresponding version no matter the target being generated. + +If the link does not include a release tag and edge guides are being generated, the domain is replaced by `edgeapi.rubyonrails.org`. For example, + +``` +http://api.rubyonrails.org/classes/ActionDispatch/Response.html +``` + +becomes + +``` +http://edgeapi.rubyonrails.org/classes/ActionDispatch/Response.html +``` + +If the link does not include a release tag and release guides are being generated, the Rails version is injected. For example, if we are generating the guides for v5.1.0 the link + +``` +http://api.rubyonrails.org/classes/ActionDispatch/Response.html +``` + +becomes + +``` +http://api.rubyonrails.org/v5.1.0/classes/ActionDispatch/Response.html +``` + +Please don't link to `edgeapi.rubyonrails.org` manually. + + API Documentation Guidelines ---------------------------- @@ -97,8 +139,6 @@ By default, guides that have not been modified are not processed, so `ONLY` is r To force processing all the guides, pass `ALL=1`. -It is also recommended that you work with `WARNINGS=1`. This detects duplicate IDs and warns about broken internal links. - If you want to generate guides in a language other than English, you can keep them in a separate directory under `source` (eg. `source/es`) and use the `GUIDES_LANGUAGE` environment variable: ``` diff --git a/guides/source/security.md b/guides/source/security.md index bb67eb75d9..a81a782cf2 100644 --- a/guides/source/security.md +++ b/guides/source/security.md @@ -377,7 +377,7 @@ In 2007 there was the first tailor-made trojan which stole information from an I Having one single place in the admin interface or Intranet, where the input has not been sanitized, makes the entire application vulnerable. Possible exploits include stealing the privileged administrator's cookie, injecting an iframe to steal the administrator's password or installing malicious software through browser security holes to take over the administrator's computer. -Refer to the Injection section for countermeasures against XSS. It is _recommended to use the SafeErb plugin_ also in an Intranet or administration interface. +Refer to the Injection section for countermeasures against XSS. **CSRF** Cross-Site Request Forgery (CSRF), also known as Cross-Site Reference Forgery (XSRF), is a gigantic attack method, it allows the attacker to do everything the administrator or Intranet user may do. As you have already seen above how CSRF works, here are a few examples of what attackers can do in the Intranet or admin interface. diff --git a/guides/source/testing.md b/guides/source/testing.md index 29a9537141..6f783089a9 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -322,7 +322,6 @@ specify to make your test failure messages clearer. | `assert_not_operator( obj1, operator, [obj2], [msg] )` | Ensures that `obj1.operator(obj2)` is false.| | `assert_predicate ( obj, predicate, [msg] )` | Ensures that `obj.predicate` is true, e.g. `assert_predicate str, :empty?`| | `assert_not_predicate ( obj, predicate, [msg] )` | Ensures that `obj.predicate` is false, e.g. `assert_not_predicate str, :empty?`| -| `assert_send( array, [msg] )` | Ensures that executing the method listed in `array[1]` on the object in `array[0]` with the parameters of `array[2 and up]` is true, e.g. assert_send [@user, :full_name, 'Sam Smith']. This one is weird eh?| | `flunk( [msg] )` | Ensures failure. This is useful to explicitly mark a test that isn't finished yet.| The above are a subset of assertions that minitest supports. For an exhaustive & diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md index dda2b12a3a..8ba00a2b10 100644 --- a/guides/source/upgrading_ruby_on_rails.md +++ b/guides/source/upgrading_ruby_on_rails.md @@ -140,6 +140,8 @@ See [#19034](https://github.com/rails/rails/pull/19034) for more details. ### Rails Controller Testing +#### Extraction of some helper methods to `rails-controller-testing` + `assigns` and `assert_template` have been extracted to the `rails-controller-testing` gem. To continue using these methods in your controller tests, add `gem 'rails-controller-testing'` to your Gemfile. @@ -147,6 +149,14 @@ your Gemfile. If you are using Rspec for testing, please see the extra configuration required in the gem's documentation. +#### New behavior when uploading files + +If you are using `ActionDispatch::Http::UploadedFile` in your tests to +upload files, you will need to change to use the similar `Rack::Test::UploadedFile` +class instead. + +See [#26404](https://github.com/rails/rails/issues/26404) for more details. + ### Autoloading is Disabled After Booting in the Production Environment Autoloading is now disabled after booting in the production environment by @@ -703,7 +713,7 @@ There are a few major changes related to JSON handling in Rails 4.1. MultiJSON has reached its [end-of-life](https://github.com/rails/rails/pull/10576) and has been removed from Rails. -If your application currently depend on MultiJSON directly, you have a few options: +If your application currently depends on MultiJSON directly, you have a few options: 1. Add 'multi_json' to your Gemfile. Note that this might cease to work in the future @@ -1278,6 +1288,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 0188f91997..7aee513a99 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,5 +1,74 @@ -* Add Yarn support in new apps using --yarn option. This adds a yarn binstop, vendor/package.json, - and the settings needed to get npm modules integrated in new apps. +* 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` + + To generate a new app that has Webpack + React configured and an example intalled: + + `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. *Liceth Ovalles*, *Guillermo Iguaran*, *DHH* 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/Rakefile b/railties/Rakefile index 202644fb26..680ed03f75 100644 --- a/railties/Rakefile +++ b/railties/Rakefile @@ -18,6 +18,7 @@ namespace :test do "lib", "#{File.dirname(__FILE__)}/../activesupport/lib", "#{File.dirname(__FILE__)}/../actionpack/lib", + "#{File.dirname(__FILE__)}/../actionview/lib", "#{File.dirname(__FILE__)}/../activemodel/lib" ] ruby "-w", "-I#{dash_i.join ':'}", file 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/application_controller.rb b/railties/lib/rails/application_controller.rb index f8394b8e0a..a98e51fd28 100644 --- a/railties/lib/rails/application_controller.rb +++ b/railties/lib/rails/application_controller.rb @@ -2,7 +2,7 @@ class Rails::ApplicationController < ActionController::Base # :nodoc: self.view_paths = File.expand_path("../templates", __FILE__) layout "application" - protected + private def require_local! unless local_request? diff --git a/railties/lib/rails/command.rb b/railties/lib/rails/command.rb index 6065e78fd1..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. @@ -82,16 +84,16 @@ module Rails [[ "rails", rails ]] + groups.sort.to_a end - protected - def command_type + private + def command_type # :doc: @command_type ||= "command" end - def lookup_paths + def lookup_paths # :doc: @lookup_paths ||= %w( rails/commands commands ) end - def file_lookup_paths + def file_lookup_paths # :doc: @file_lookup_paths ||= [ "{#{lookup_paths.join(',')}}", "**", "*_command.rb" ] end end 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/command/behavior.rb b/railties/lib/rails/command/behavior.rb index 2e8517070c..4a92f72f16 100644 --- a/railties/lib/rails/command/behavior.rb +++ b/railties/lib/rails/command/behavior.rb @@ -16,13 +16,13 @@ module Rails @subclasses ||= [] end - protected + private # This code is based directly on the Text gem implementation. # Copyright (c) 2006-2013 Paul Battley, Michael Neumann, Tim Fletcher. # # Returns a value representing the "cost" of transforming str1 into str2. - def levenshtein_distance(str1, str2) + def levenshtein_distance(str1, str2) # :doc: s = str1 t = str2 n = s.length @@ -58,7 +58,7 @@ module Rails end # Prints a list of generators. - def print_list(base, namespaces) #:nodoc: + def print_list(base, namespaces) return if namespaces.empty? puts "#{base.camelize}:" @@ -71,7 +71,7 @@ module Rails # Receives namespaces in an array and tries to find matching generators # in the load path. - def lookup(namespaces) #:nodoc: + def lookup(namespaces) paths = namespaces_to_paths(namespaces) paths.each do |raw_path| @@ -91,7 +91,7 @@ module Rails end # This will try to load any command in the load path to show in help. - def lookup! #:nodoc: + def lookup! $LOAD_PATH.each do |base| Dir[File.join(base, *file_lookup_paths)].each do |path| begin @@ -107,7 +107,7 @@ module Rails # Convert namespaces to paths by replacing ":" for "/" and adding # an extra lookup. For example, "rails:model" should be searched # in both: "rails/model/model_generator" and "rails/model_generator". - def namespaces_to_paths(namespaces) #:nodoc: + def namespaces_to_paths(namespaces) paths = [] namespaces.each do |namespace| pieces = namespace.split(":") diff --git a/railties/lib/rails/commands/dbconsole/dbconsole_command.rb b/railties/lib/rails/commands/dbconsole/dbconsole_command.rb index 35e8673215..588fb06b15 100644 --- a/railties/lib/rails/commands/dbconsole/dbconsole_command.rb +++ b/railties/lib/rails/commands/dbconsole/dbconsole_command.rb @@ -99,14 +99,14 @@ module Rails Rails.respond_to?(:env) ? Rails.env : Rails::Command.environment end - protected - def configurations + private + def configurations # :doc: require APP_PATH ActiveRecord::Base.configurations = Rails.application.config.database_configuration ActiveRecord::Base.configurations end - def find_cmd_and_exec(commands, *args) + def find_cmd_and_exec(commands, *args) # :doc: commands = Array(commands) dirs_on_path = ENV["PATH"].to_s.split(File::PATH_SEPARATOR) diff --git a/railties/lib/rails/commands/generate/generate_command.rb b/railties/lib/rails/commands/generate/generate_command.rb index 59b2febc43..aa8dab71b0 100644 --- a/railties/lib/rails/commands/generate/generate_command.rb +++ b/railties/lib/rails/commands/generate/generate_command.rb @@ -14,6 +14,8 @@ module Rails require_application_and_environment! load_generators + ARGV.shift + Rails::Generators.invoke generator, args, behavior: :invoke, destination_root: Rails::Command.root 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/plugin/plugin_command.rb b/railties/lib/rails/commands/plugin/plugin_command.rb index 16587ce067..b40ab006af 100644 --- a/railties/lib/rails/commands/plugin/plugin_command.rb +++ b/railties/lib/rails/commands/plugin/plugin_command.rb @@ -11,7 +11,7 @@ module Rails "#{executable} new [options]" end - class_option :rc, type: :boolean, default: File.join("~", ".railsrc"), + class_option :rc, type: :string, default: File.join("~", ".railsrc"), desc: "Initialize the plugin command with previous defaults. Uses .railsrc in your home directory by default." class_option :no_rc, desc: "Skip evaluating .railsrc." 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 e56f6159ad..dc0b158bd4 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) @@ -643,23 +643,24 @@ module Rails protected - def load_config_initializer(initializer) - ActiveSupport::Notifications.instrument("load_config_initializer.railties", initializer: initializer) do - load(initializer) - end - end - def run_tasks_blocks(*) #:nodoc: super paths["lib/tasks"].existent.sort.each { |ext| load(ext) } end - def has_migrations? #:nodoc: + private + + def load_config_initializer(initializer) # :doc: + ActiveSupport::Notifications.instrument("load_config_initializer.railties", initializer: initializer) do + load(initializer) + end + end + + def has_migrations? paths["db/migrate"].existent.any? end def self.find_root_with_flag(flag, root_path, default = nil) #:nodoc: - while root_path && File.directory?(root_path) && !File.exist?("#{root_path}/#{flag}") parent = File.dirname(root_path) root_path = parent != root_path && parent @@ -671,24 +672,22 @@ module Rails Pathname.new File.realpath root end - def default_middleware_stack #:nodoc: + def default_middleware_stack ActionDispatch::MiddlewareStack.new end - def _all_autoload_once_paths #:nodoc: + def _all_autoload_once_paths config.autoload_once_paths end - def _all_autoload_paths #:nodoc: + def _all_autoload_paths @_all_autoload_paths ||= (config.autoload_paths + config.eager_load_paths + config.autoload_once_paths).uniq end - def _all_load_paths #:nodoc: + def _all_load_paths @_all_load_paths ||= (config.paths.load_paths + _all_autoload_paths).uniq end - private - def build_request(env) env.merge!(env_config) req = ActionDispatch::Request.new env 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 67037106e5..99bda728ee 100644 --- a/railties/lib/rails/generators.rb +++ b/railties/lib/rails/generators.rb @@ -67,246 +67,247 @@ module Rails } } - def self.configure!(config) #:nodoc: - api_only! if config.api_only - no_color! unless config.colorize_logging - aliases.deep_merge! config.aliases - options.deep_merge! config.options - fallbacks.merge! config.fallbacks - templates_path.concat config.templates - templates_path.uniq! - hide_namespaces(*config.hidden_namespaces) - end + class << self + def configure!(config) #:nodoc: + api_only! if config.api_only + no_color! unless config.colorize_logging + aliases.deep_merge! config.aliases + options.deep_merge! config.options + fallbacks.merge! config.fallbacks + templates_path.concat config.templates + templates_path.uniq! + hide_namespaces(*config.hidden_namespaces) + end - def self.templates_path #:nodoc: - @templates_path ||= [] - end + def templates_path #:nodoc: + @templates_path ||= [] + end - def self.aliases #:nodoc: - @aliases ||= DEFAULT_ALIASES.dup - end + def aliases #:nodoc: + @aliases ||= DEFAULT_ALIASES.dup + end - def self.options #:nodoc: - @options ||= DEFAULT_OPTIONS.dup - end + def options #:nodoc: + @options ||= DEFAULT_OPTIONS.dup + end - # Hold configured generators fallbacks. If a plugin developer wants a - # generator group to fallback to another group in case of missing generators, - # they can add a fallback. - # - # For example, shoulda is considered a test_framework and is an extension - # of test_unit. However, most part of shoulda generators are similar to - # test_unit ones. - # - # Shoulda then can tell generators to search for test_unit generators when - # some of them are not available by adding a fallback: - # - # Rails::Generators.fallbacks[:shoulda] = :test_unit - def self.fallbacks - @fallbacks ||= {} - end + # Hold configured generators fallbacks. If a plugin developer wants a + # generator group to fallback to another group in case of missing generators, + # they can add a fallback. + # + # For example, shoulda is considered a test_framework and is an extension + # of test_unit. However, most part of shoulda generators are similar to + # test_unit ones. + # + # Shoulda then can tell generators to search for test_unit generators when + # some of them are not available by adding a fallback: + # + # Rails::Generators.fallbacks[:shoulda] = :test_unit + def fallbacks + @fallbacks ||= {} + end - # Configure generators for API only applications. It basically hides - # everything that is usually browser related, such as assets and session - # migration generators, and completely disable helpers and assets - # so generators such as scaffold won't create them. - def self.api_only! - hide_namespaces "assets", "helper", "css", "js" - - options[:rails].merge!( - api: true, - assets: false, - helper: false, - template_engine: nil - ) - - if ARGV.first == "mailer" - options[:rails].merge!(template_engine: :erb) + # Configure generators for API only applications. It basically hides + # everything that is usually browser related, such as assets and session + # migration generators, and completely disable helpers and assets + # so generators such as scaffold won't create them. + def api_only! + hide_namespaces "assets", "helper", "css", "js" + + options[:rails].merge!( + api: true, + assets: false, + helper: false, + template_engine: nil + ) + + if ARGV.first == "mailer" + options[:rails].merge!(template_engine: :erb) + end end - end - # Remove the color from output. - def self.no_color! - Thor::Base.shell = Thor::Shell::Basic - end + # Remove the color from output. + def no_color! + Thor::Base.shell = Thor::Shell::Basic + end - # Returns an array of generator namespaces that are hidden. - # Generator namespaces may be hidden for a variety of reasons. - # Some are aliased such as "rails:migration" and can be - # invoked with the shorter "migration", others are private to other generators - # such as "css:scaffold". - def self.hidden_namespaces - @hidden_namespaces ||= begin - orm = options[:rails][:orm] - test = options[:rails][:test_framework] - template = options[:rails][:template_engine] - css = options[:rails][:stylesheet_engine] - - [ - "rails", - "resource_route", - "#{orm}:migration", - "#{orm}:model", - "#{test}:controller", - "#{test}:helper", - "#{test}:integration", - "#{test}:mailer", - "#{test}:model", - "#{test}:scaffold", - "#{test}:view", - "#{test}:job", - "#{template}:controller", - "#{template}:scaffold", - "#{template}:mailer", - "#{css}:scaffold", - "#{css}:assets", - "css:assets", - "css:scaffold" - ] + # Returns an array of generator namespaces that are hidden. + # Generator namespaces may be hidden for a variety of reasons. + # Some are aliased such as "rails:migration" and can be + # invoked with the shorter "migration", others are private to other generators + # such as "css:scaffold". + def hidden_namespaces + @hidden_namespaces ||= begin + orm = options[:rails][:orm] + test = options[:rails][:test_framework] + template = options[:rails][:template_engine] + css = options[:rails][:stylesheet_engine] + + [ + "rails", + "resource_route", + "#{orm}:migration", + "#{orm}:model", + "#{test}:controller", + "#{test}:helper", + "#{test}:integration", + "#{test}:mailer", + "#{test}:model", + "#{test}:scaffold", + "#{test}:view", + "#{test}:job", + "#{template}:controller", + "#{template}:scaffold", + "#{template}:mailer", + "#{css}:scaffold", + "#{css}:assets", + "css:assets", + "css:scaffold" + ] + end end - end - class << self def hide_namespaces(*namespaces) hidden_namespaces.concat(namespaces) end alias hide_namespace hide_namespaces - end - # Show help message with available generators. - def self.help(command = "generate") - puts "Usage: rails #{command} GENERATOR [args] [options]" - puts - puts "General options:" - puts " -h, [--help] # Print generator's options and usage" - puts " -p, [--pretend] # Run but do not make any changes" - puts " -f, [--force] # Overwrite files that already exist" - puts " -s, [--skip] # Skip files that already exist" - puts " -q, [--quiet] # Suppress status output" - puts - puts "Please choose a generator below." - puts - - print_generators - end - - def self.public_namespaces - lookup! - subclasses.map(&:namespace) - end - - def self.print_generators - sorted_groups.each { |b, n| print_list(b, n) } - end + # Show help message with available generators. + def help(command = "generate") + puts "Usage: rails #{command} GENERATOR [args] [options]" + puts + puts "General options:" + puts " -h, [--help] # Print generator's options and usage" + puts " -p, [--pretend] # Run but do not make any changes" + puts " -f, [--force] # Overwrite files that already exist" + puts " -s, [--skip] # Skip files that already exist" + puts " -q, [--quiet] # Suppress status output" + puts + puts "Please choose a generator below." + puts + + print_generators + end - def self.sorted_groups - namespaces = public_namespaces - namespaces.sort! + def public_namespaces + lookup! + subclasses.map(&:namespace) + end - groups = Hash.new { |h, k| h[k] = [] } - namespaces.each do |namespace| - base = namespace.split(":").first - groups[base] << namespace + def print_generators + sorted_groups.each { |b, n| print_list(b, n) } end - rails = groups.delete("rails") - rails.map! { |n| n.sub(/^rails:/, "") } - rails.delete("app") - rails.delete("plugin") + def sorted_groups + namespaces = public_namespaces + namespaces.sort! - hidden_namespaces.each { |n| groups.delete(n.to_s) } + groups = Hash.new { |h, k| h[k] = [] } + namespaces.each do |namespace| + base = namespace.split(":").first + groups[base] << namespace + end - [[ "rails", rails ]] + groups.sort.to_a - end + rails = groups.delete("rails") + rails.map! { |n| n.sub(/^rails:/, "") } + rails.delete("app") + rails.delete("plugin") - # 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. - # - # find_by_namespace :webrat, :rails, :integration - # - # Will search for the following generators: - # - # "rails:webrat", "webrat:integration", "webrat" - # - # Notice that "rails:generators:webrat" could be loaded as well, what - # Rails looks for is the first and last parts of the namespace. - def self.find_by_namespace(name, base = nil, context = nil) #:nodoc: - lookups = [] - lookups << "#{base}:#{name}" if base - lookups << "#{name}:#{context}" if context - - unless base || context - unless name.to_s.include?(?:) - lookups << "#{name}:#{name}" - lookups << "rails:#{name}" - end - lookups << "#{name}" + hidden_namespaces.each { |n| groups.delete(n.to_s) } + + [[ "rails", rails ]] + groups.sort.to_a end - lookup(lookups) + # 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. + # + # find_by_namespace :webrat, :rails, :integration + # + # Will search for the following generators: + # + # "rails:webrat", "webrat:integration", "webrat" + # + # Notice that "rails:generators:webrat" could be loaded as well, what + # Rails looks for is the first and last parts of the namespace. + def find_by_namespace(name, base = nil, context = nil) #:nodoc: + lookups = [] + lookups << "#{base}:#{name}" if base + lookups << "#{name}:#{context}" if context + + unless base || context + unless name.to_s.include?(?:) + lookups << "#{name}:#{name}" + lookups << "rails:#{name}" + end + lookups << "#{name}" + end - namespaces = Hash[subclasses.map { |klass| [klass.namespace, klass] }] - lookups.each do |namespace| + lookup(lookups) - klass = namespaces[namespace] - return klass if klass - end + namespaces = Hash[subclasses.map { |klass| [klass.namespace, klass] }] + lookups.each do |namespace| - invoke_fallbacks_for(name, base) || invoke_fallbacks_for(context, name) - end + klass = namespaces[namespace] + return klass if klass + end - # Receives a namespace, arguments and the behavior to invoke the generator. - # It's used as the default entry point for generate, destroy and update - # commands. - def self.invoke(namespace, args = ARGV, config = {}) - names = namespace.to_s.split(":") - if klass = find_by_namespace(names.pop, names.any? && names.join(":")) - args << "--help" if args.empty? && klass.arguments.any?(&:required?) - klass.start(args, config) - else - options = sorted_groups.flat_map(&:last) - suggestions = options.sort_by { |suggested| levenshtein_distance(namespace.to_s, suggested) }.first(3) - msg = "Could not find generator '#{namespace}'. " - msg << "Maybe you meant #{ suggestions.map { |s| "'#{s}'" }.to_sentence(last_word_connector: " or ", locale: :en) }\n" - msg << "Run `rails generate --help` for more options." - puts msg + invoke_fallbacks_for(name, base) || invoke_fallbacks_for(context, name) end - end - protected - def self.print_list(base, namespaces) - namespaces = namespaces.reject { |n| hidden_namespaces.include?(n) } - super + # Receives a namespace, arguments and the behavior to invoke the generator. + # It's used as the default entry point for generate, destroy and update + # commands. + def invoke(namespace, args = ARGV, config = {}) + names = namespace.to_s.split(":") + if klass = find_by_namespace(names.pop, names.any? && names.join(":")) + args << "--help" if args.empty? && klass.arguments.any?(&:required?) + klass.start(args, config) + else + options = sorted_groups.flat_map(&:last) + suggestions = options.sort_by { |suggested| levenshtein_distance(namespace.to_s, suggested) }.first(3) + msg = "Could not find generator '#{namespace}'. " + msg << "Maybe you meant #{ suggestions.map { |s| "'#{s}'" }.to_sentence(last_word_connector: " or ", locale: :en) }\n" + msg << "Run `rails generate --help` for more options." + puts msg + end end - # Try fallbacks for the given base. - def self.invoke_fallbacks_for(name, base) #:nodoc: - return nil unless base && fallbacks[base.to_sym] - invoked_fallbacks = [] - - Array(fallbacks[base.to_sym]).each do |fallback| - next if invoked_fallbacks.include?(fallback) - invoked_fallbacks << fallback + private - klass = find_by_namespace(name, fallback) - return klass if klass + def print_list(base, namespaces) # :doc: + namespaces = namespaces.reject { |n| hidden_namespaces.include?(n) } + super end - nil - end + # Try fallbacks for the given base. + def invoke_fallbacks_for(name, base) + return nil unless base && fallbacks[base.to_sym] + invoked_fallbacks = [] - def self.command_type - @command_type ||= "generator" - end + Array(fallbacks[base.to_sym]).each do |fallback| + next if invoked_fallbacks.include?(fallback) + invoked_fallbacks << fallback - def self.lookup_paths - @lookup_paths ||= %w( rails/generators generators ) - end + klass = find_by_namespace(name, fallback) + return klass if klass + end - def self.file_lookup_paths - @file_lookup_paths ||= [ "{#{lookup_paths.join(',')}}", "**", "*_generator.rb" ] - end + nil + end + + def command_type # :doc: + @command_type ||= "generator" + end + + def lookup_paths # :doc: + @lookup_paths ||= %w( rails/generators generators ) + end + + def file_lookup_paths # :doc: + @file_lookup_paths ||= [ "{#{lookup_paths.join(',')}}", "**", "*_generator.rb" ] + end + end end end diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb index 8a60560f86..0bd0615b7e 100644 --- a/railties/lib/rails/generators/actions.rb +++ b/railties/lib/rails/generators/actions.rb @@ -260,23 +260,23 @@ module Rails @after_bundle_callbacks << block end - protected + private # Define log for backwards compatibility. If just one argument is sent, # invoke say, otherwise invoke say_status. Differently from say and # similarly to say_status, this method respects the quiet? option given. - def log(*args) + def log(*args) # :doc: 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 # Runs the supplied command using either "rake ..." or "rails ..." # based on the executor parameter provided. - def execute_command(executor, command, options = {}) + def execute_command(executor, command, options = {}) # :doc: log executor, command env = options[:env] || ENV["RAILS_ENV"] || "development" sudo = options[:sudo] && !Gem.win_platform? ? "sudo " : "" @@ -284,7 +284,7 @@ module Rails end # Add an extension to the given name based on the platform. - def extify(name) + def extify(name) # :doc: if Gem.win_platform? "#{name}.bat" else @@ -294,7 +294,7 @@ module Rails # Surround string with single quotes if there is no quotes. # Otherwise fall back to double quotes - def quote(value) + def quote(value) # :doc: return value.inspect unless value.is_a? String if value.include?("'") diff --git a/railties/lib/rails/generators/actions/create_migration.rb b/railties/lib/rails/generators/actions/create_migration.rb index 587c61fd42..d06609e91e 100644 --- a/railties/lib/rails/generators/actions/create_migration.rb +++ b/railties/lib/rails/generators/actions/create_migration.rb @@ -37,9 +37,9 @@ module Rails end alias :exists? :existing_migration - protected + private - def on_conflict_behavior + def on_conflict_behavior # :doc: options = base.options.merge(config) if identical? say_status :identical, :blue, relative_existing_migration @@ -54,13 +54,13 @@ 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 - def say_status(status, color, message = relative_destination) + def say_status(status, color, message = relative_destination) # :doc: base.shell.say_status(status, message, color) if config[:verbose] end end diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 87f6f01750..ea88afe9f4 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -33,15 +33,15 @@ module Rails class_option :javascript, type: :string, aliases: "-j", desc: "Preconfigure for selected JavaScript library" - class_option :yarn, type: :boolean, default: false, - desc: "Preconfigure for assets management with Yarn" + class_option :webpack, type: :string, default: nil, + desc: "Preconfigure for app-like JavaScript with Webpack" + + class_option :skip_yarn, type: :boolean, default: false, + desc: "Don't use Yarn for managing JavaScript dependencies" 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" @@ -105,9 +105,9 @@ module Rails convert_database_option_for_jruby end - protected + private - def gemfile_entry(name, *args) + def gemfile_entry(name, *args) # :doc: options = args.extract_options! version = args.first github = options[:github] @@ -123,11 +123,12 @@ module Rails self end - def gemfile_entries + def gemfile_entries # :doc: [rails_gemfile_entry, database_gemfile_entry, webserver_gemfile_entry, assets_gemfile_entry, + webpacker_gemfile_entry, javascript_gemfile_entry, jbuilder_gemfile_entry, psych_gemfile_entry, @@ -135,13 +136,13 @@ module Rails @extra_entries].flatten.find_all(&@gem_filter) end - def add_gem_entry_filter + def add_gem_entry_filter # :doc: @gem_filter = lambda { |next_filter, entry| yield(entry) && next_filter.call(entry) }.curry[@gem_filter] end - def builder + def builder # :doc: @builder ||= begin builder_class = get_builder_class builder_class.include(ActionMethods) @@ -149,24 +150,24 @@ module Rails end end - def build(meth, *args) + def build(meth, *args) # :doc: builder.send(meth, *args) if builder.respond_to?(meth) end - def create_root + def create_root # :doc: valid_const? empty_directory "." FileUtils.cd(destination_root) unless options[:pretend] end - def apply_rails_template + def apply_rails_template # :doc: apply rails_template if rails_template rescue Thor::Error, LoadError, Errno::ENOENT => e raise Error, "The template [#{rails_template}] could not be loaded. Error: #{e}" end - def set_default_accessors! + def set_default_accessors! # :doc: self.destination_root = File.expand_path(app_path, destination_root) self.rails_template = \ case options[:template] @@ -179,32 +180,32 @@ module Rails end end - def database_gemfile_entry + def database_gemfile_entry # :doc: return [] if options[:skip_active_record] gem_name, gem_version = gem_for_database GemfileEntry.version gem_name, gem_version, "Use #{options[:database]} as the database for Active Record" end - def webserver_gemfile_entry + def webserver_gemfile_entry # :doc: return [] if options[:skip_puma] comment = "Use Puma as the app server" GemfileEntry.new("puma", "~> 3.0", comment) end - def include_all_railties? + def include_all_railties? # :doc: options.values_at(:skip_active_record, :skip_action_mailer, :skip_test, :skip_sprockets, :skip_action_cable).none? end - def comment_if(value) + def comment_if(value) # :doc: options[value] ? "# " : "" end - def keeps? + def keeps? # :doc: !options[:skip_keeps] end - def sqlite3? + def sqlite3? # :doc: !options[:skip_active_record] && options[:database] == "sqlite3" end @@ -242,6 +243,7 @@ module Rails def rails_gemfile_entry dev_edge_common = [ + GemfileEntry.github("arel", "rails/arel") ] if options.dev? [ @@ -315,6 +317,13 @@ module Rails gems end + def webpacker_gemfile_entry + return [] unless options[:webpack] + + comment = "Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker" + GemfileEntry.github "webpacker", "rails/webpacker", nil, comment + end + def jbuilder_gemfile_entry comment = "Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder" GemfileEntry.new "jbuilder", "~> 2.5", comment, {}, options[:api] @@ -414,52 +423,10 @@ module Rails bundle_command("install") if bundle_install? end - def run_yarn - if package_json_exist? - if yarn_path - say_status :run, "yarn install" - yarn_command("install") - else - say_status :warning, "yarn option passed but Yarn executable was not detected in the system.", :yellow - say_status :warning, "Download Yarn at https://yarnpkg.com/en/docs/install", :yellow - end - end - end - - def package_json_exist? - File.exist?("vendor/package.json") - end - - def yarn_path - commands = ["yarn"] - - if Gem.win_platform? - ENV["PATHEXT"].split(File::PATH_SEPARATOR).each do |ext| - commands << commands[0] + ext - end - end - - yarn_path = commands.find do |cmd| - paths = ENV["PATH"].split(File::PATH_SEPARATOR) - - path = paths.find do |p| - full_path = File.expand_path(cmd, p) - File.executable?(full_path) && File.file?(full_path) - end - - path && File.expand_path(cmd, path) - end - - yarn_path - end - - def yarn_command(command) - full_command = "#{yarn_path} #{command}" - - if options[:quiet] - system(full_command, out: File::NULL) - else - system(full_command) + def run_webpack + if !(webpack = options[:webpack]).nil? + rails_command "webpacker:install" + rails_command "webpacker:install:#{webpack}" unless webpack == "webpack" end end diff --git a/railties/lib/rails/generators/base.rb b/railties/lib/rails/generators/base.rb index c707bbfcbf..a650c52626 100644 --- a/railties/lib/rails/generators/base.rb +++ b/railties/lib/rails/generators/base.rb @@ -239,11 +239,11 @@ module Rails end end - protected + private # Check whether the given class names are already taken by user # application or Ruby on Rails. - def class_collisions(*class_names) #:nodoc: + def class_collisions(*class_names) return unless behavior == :invoke class_names.flatten.each do |class_name| @@ -256,15 +256,15 @@ 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 end # Takes in an array of nested modules and extracts the last module - def extract_last_module(nesting) + def extract_last_module(nesting) # :doc: nesting.inject(Object) do |last_module, nest| break unless last_module.const_defined?(nest, false) last_module.const_get(nest) @@ -272,12 +272,12 @@ module Rails end # Use Rails default banner. - def self.banner + def self.banner # :doc: "rails generate #{namespace.sub(/^rails:/, '')} #{arguments.map(&:usage).join(' ')} [options]".gsub(/\s+/, " ") end # Sets the base_name taking into account the current class namespace. - def self.base_name + def self.base_name # :doc: @base_name ||= begin if base = name.to_s.split("::").first base.underscore @@ -287,7 +287,7 @@ module Rails # Removes the namespaces and get the generator name. For example, # Rails::Generators::ModelGenerator will return "model" as generator name. - def self.generator_name + def self.generator_name # :doc: @generator_name ||= begin if generator = name.to_s.split("::").last generator.sub!(/Generator$/, "") @@ -298,18 +298,18 @@ module Rails # Returns the default value for the option name given doing a lookup in # Rails::Generators.options. - def self.default_value_for_option(name, options) + def self.default_value_for_option(name, options) # :doc: default_for_option(Rails::Generators.options, name, options, options[:default]) end # Returns default aliases for the option name given doing a lookup in # Rails::Generators.aliases. - def self.default_aliases_for_option(name, options) + def self.default_aliases_for_option(name, options) # :doc: default_for_option(Rails::Generators.aliases, name, options, options[:aliases]) end # Returns default for the option name given doing a lookup in config. - def self.default_for_option(config, name, options, default) + def self.default_for_option(config, name, options, default) # :doc: if generator_name && (c = config[generator_name.to_sym]) && c.key?(name) c[name] elsif base_name && (c = config[base_name.to_sym]) && c.key?(name) @@ -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) @@ -343,7 +343,7 @@ module Rails # Small macro to add ruby as an option to the generator with proper # default value plus an instance helper method called shebang. - def self.add_shebang_option! + def self.add_shebang_option! # :doc: class_option :ruby, type: :string, aliases: "-r", default: Thor::Util.ruby_command, desc: "Path to the Ruby binary of your choice", banner: "PATH" @@ -361,7 +361,7 @@ module Rails } end - def self.usage_path + def self.usage_path # :doc: paths = [ source_root && File.expand_path("../USAGE", source_root), default_generator_root && File.join(default_generator_root, "USAGE") @@ -369,7 +369,7 @@ module Rails paths.compact.detect { |path| File.exist? path } end - def self.default_generator_root + def self.default_generator_root # :doc: path = File.expand_path(File.join(base_name, generator_name), base_root) path if File.exist?(path) end diff --git a/railties/lib/rails/generators/erb.rb b/railties/lib/rails/generators/erb.rb index d01502002f..d5e326d6ee 100644 --- a/railties/lib/rails/generators/erb.rb +++ b/railties/lib/rails/generators/erb.rb @@ -3,7 +3,7 @@ require "rails/generators/named_base" module Erb # :nodoc: module Generators # :nodoc: class Base < Rails::Generators::NamedBase #:nodoc: - protected + private def formats [format] diff --git a/railties/lib/rails/generators/erb/mailer/mailer_generator.rb b/railties/lib/rails/generators/erb/mailer/mailer_generator.rb index f150240908..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 @@ -26,7 +26,7 @@ module Erb # :nodoc: end end - protected + private def formats [:text, :html] diff --git a/railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb index 154d85f381..0d77ef21da 100644 --- a/railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb +++ b/railties/lib/rails/generators/erb/scaffold/scaffold_generator.rb @@ -21,7 +21,7 @@ module Erb # :nodoc: end end - protected + private def available_views %w(index edit show new _form) 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 45f2fba5b9..6f1925928b 100644 --- a/railties/lib/rails/generators/named_base.rb +++ b/railties/lib/rails/generators/named_base.rb @@ -32,145 +32,145 @@ module Rails end 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 :file_name + private + # FIXME: We are avoiding to use alias because a bug on thor that make # this method public and add it to the task list. - def singular_name + def singular_name # :doc: file_name end # Wrap block with namespace of current application # if namespace exists and is not skipped - def module_namespacing(&block) + def module_namespacing(&block) # :doc: content = capture(&block) content = wrap_with_namespace(content) if namespaced? concat(content) end - def indent(content, multiplier = 2) + def indent(content, multiplier = 2) # :doc: spaces = " " * multiplier content.each_line.map { |line| line.blank? ? line : "#{spaces}#{line}" }.join end - def wrap_with_namespace(content) + def wrap_with_namespace(content) # :doc: content = indent(content).chomp "module #{namespace.name}\n#{content}\nend\n" end - def inside_template + def inside_template # :doc: @inside_template = true yield ensure @inside_template = false end - def inside_template? + def inside_template? # :doc: @inside_template end - def namespace + def namespace # :doc: Rails::Generators.namespace end - def namespaced? + def namespaced? # :doc: !options[:skip_namespace] && namespace end - def file_path + def file_path # :doc: @file_path ||= (class_path + [file_name]).join("/") end - def class_path + def class_path # :doc: inside_template? || !namespaced? ? regular_class_path : namespaced_class_path end - def regular_class_path + def regular_class_path # :doc: @class_path end - def namespaced_file_path - @namespaced_file_path ||= namespaced_class_path.join("/") - end - - def namespaced_class_path + def namespaced_class_path # :doc: @namespaced_class_path ||= [namespaced_path] + @class_path end - def namespaced_path + def namespaced_path # :doc: @namespaced_path ||= namespace.name.split("::").first.underscore end - def class_name + def class_name # :doc: (class_path + [file_name]).map!(&:camelize).join("::") end - def human_name + def human_name # :doc: @human_name ||= singular_name.humanize end - def plural_name + def plural_name # :doc: @plural_name ||= singular_name.pluralize end - def i18n_scope + def i18n_scope # :doc: @i18n_scope ||= file_path.tr("/", ".") end - def table_name + def table_name # :doc: @table_name ||= begin base = pluralize_table_names? ? plural_name : singular_name (class_path + [base]).join("_") end end - def uncountable? + def uncountable? # :doc: singular_name == plural_name end - def index_helper + def index_helper # :doc: uncountable? ? "#{plural_table_name}_index" : plural_table_name end - def show_helper + def show_helper # :doc: "#{singular_table_name}_url(@#{singular_table_name})" end - def edit_helper + def edit_helper # :doc: "edit_#{show_helper}" end - def new_helper + def new_helper # :doc: "new_#{singular_table_name}_url" end - def singular_table_name + def singular_table_name # :doc: @singular_table_name ||= (pluralize_table_names? ? table_name.singularize : table_name) end - def plural_table_name + def plural_table_name # :doc: @plural_table_name ||= (pluralize_table_names? ? table_name : table_name.pluralize) end - def plural_file_name + def plural_file_name # :doc: @plural_file_name ||= file_name.pluralize end - def fixture_file_name + def fixture_file_name # :doc: @fixture_file_name ||= (pluralize_table_names? ? plural_file_name : file_name) end - def route_url + def route_url # :doc: @route_url ||= class_path.collect { |dname| "/" + dname }.join + "/" + plural_file_name end - def url_helper_prefix + def url_helper_prefix # :doc: @url_helper_prefix ||= (class_path + [file_name]).join("_") end # Tries to retrieve the application name or simply return application. - def application_name + def application_name # :doc: if defined?(Rails) && Rails.application Rails.application.class.name.split("::").first.underscore else @@ -178,20 +178,20 @@ module Rails end end - def assign_names!(name) #:nodoc: + def assign_names!(name) @class_path = name.include?("/") ? name.split("/") : name.split("::") @class_path.map!(&:underscore) @file_name = @class_path.pop end # Convert attributes array into GeneratedAttribute objects. - def parse_attributes! #:nodoc: + def parse_attributes! self.attributes = (attributes || []).map do |attr| Rails::Generators::GeneratedAttribute.parse(attr) end end - def attributes_names + def attributes_names # :doc: @attributes_names ||= attributes.each_with_object([]) do |a, names| names << a.column_name names << "password_confirmation" if a.password_digest? @@ -199,11 +199,11 @@ module Rails end end - def pluralize_table_names? + def pluralize_table_names? # :doc: !defined?(ActiveRecord::Base) || ActiveRecord::Base.pluralize_table_names end - def mountable_engine? + def mountable_engine? # :doc: defined?(ENGINE_ROOT) && namespaced? end @@ -217,9 +217,9 @@ module Rails # If the generator is invoked with class name Admin, it will check for # the presence of "AdminDecorator". # - def self.check_class_collision(options = {}) + 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 49d23d682a..3cf923faf0 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -53,8 +53,10 @@ module Rails template "gitignore", ".gitignore" end - def packagejson - template "package.json", "vendor/package.json" + def version_control + if !options[:skip_git] && !options[:pretend] + run "git init" + end end def app @@ -155,23 +157,12 @@ module Rails end def vendor - if options[:yarn] - empty_directory_with_keep_file "vendor" - else - vendor_javascripts - vendor_stylesheets - end - end + empty_directory_with_keep_file "vendor" - def vendor_javascripts - unless options[:skip_javascript] - empty_directory_with_keep_file "vendor/assets/javascripts" + unless options[:skip_yarn] + template "package.json", "vendor/package.json" end end - - def vendor_stylesheets - empty_directory_with_keep_file "vendor/assets/stylesheets" - end end module Generators @@ -190,20 +181,21 @@ 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 - # Force sprockets to be skipped when generating API only apps. + # Force sprockets and yarn to be skipped when generating API only apps. # Can't modify options hash as it's frozen by default. - self.options = options.merge(skip_sprockets: true, skip_javascript: true).freeze if options[:api] + if options[:api] + self.options = options.merge(skip_sprockets: true, skip_javascript: true, skip_yarn: true).freeze + end end public_task :set_default_accessors! @@ -213,9 +205,9 @@ module Rails build(:readme) build(:rakefile) build(:configru) - build(:packagejson) if options[:yarn] - build(:gitignore) unless options[:skip_git] - build(:gemfile) unless options[:skip_gemfile] + build(:gitignore) unless options[:skip_git] + build(:gemfile) unless options[:skip_gemfile] + build(:version_control) end def create_app_files @@ -276,6 +268,10 @@ module Rails def create_vendor_files build(:vendor) + + if options[:skip_yarn] + remove_file "vendor/package.json" + end end def delete_app_assets_if_api_option @@ -283,7 +279,6 @@ module Rails remove_dir "app/assets" remove_dir "lib/assets" remove_dir "tmp/cache/assets" - remove_dir "vendor/assets" end end @@ -331,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" @@ -359,23 +353,27 @@ module Rails end end + def delete_bin_yarn_if_skip_yarn_option + remove_file "bin/yarn" if options[:skip_yarn] + end + def finish_template build(:leftovers) end public_task :apply_rails_template, :run_bundle - public_task :run_yarn, :generate_spring_binstubs + public_task :run_webpack, :generate_spring_binstubs def run_after_bundle_callbacks @after_bundle_callbacks.each(&:call) end - protected - def self.banner "rails new #{arguments.map(&:usage).join(' ')} [options]" end + private + # Define file as an alias to create_file for backwards compatibility. def file(*args, &block) create_file(*args, &block) 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/app/assets/javascripts/application.js.tt b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt index 8db5b7e075..25870f19c8 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt +++ b/railties/lib/rails/generators/rails/app/templates/app/assets/javascripts/application.js.tt @@ -1,8 +1,8 @@ // This is a manifest file that'll be compiled into application.js, which will include all the files // listed below. // -// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, -// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path. +// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's +// vendor/assets/javascripts directory can be referenced here using a relative path. // // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the // compiled file. JavaScript code in this file should be added after the last require_* statement. diff --git a/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css index 0ebd7fe829..865300bef9 100644 --- a/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css +++ b/railties/lib/rails/generators/rails/app/templates/app/assets/stylesheets/application.css @@ -2,8 +2,8 @@ * This is a manifest file that'll be compiled into application.css, which will include all the files * listed below. * - * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, - * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. + * Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's + * vendor/assets/stylesheets directory can be referenced here using a relative path. * * You're free to add application-wide styles to this file and they'll appear at the bottom of the * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS diff --git a/railties/lib/rails/generators/rails/app/templates/bin/setup.tt b/railties/lib/rails/generators/rails/app/templates/bin/setup.tt index 73b0732463..c6607dbb2b 100644 --- a/railties/lib/rails/generators/rails/app/templates/bin/setup.tt +++ b/railties/lib/rails/generators/rails/app/templates/bin/setup.tt @@ -16,8 +16,9 @@ chdir APP_ROOT do puts '== Installing dependencies ==' system! 'gem install bundler --conservative' system('bundle check') || system!('bundle install') -<% if options[:yarn] %> - system! 'bin/yarn' +<% unless options[:skip_yarn] %> + # Install JavaScript dependencies if using Yarn + # system('bin/yarn') <% end %> <% unless options.skip_active_record -%> diff --git a/railties/lib/rails/generators/rails/app/templates/bin/update.tt b/railties/lib/rails/generators/rails/app/templates/bin/update.tt index dcd80b6ce5..d23af018c7 100644 --- a/railties/lib/rails/generators/rails/app/templates/bin/update.tt +++ b/railties/lib/rails/generators/rails/app/templates/bin/update.tt @@ -16,10 +16,6 @@ chdir APP_ROOT do puts '== Installing dependencies ==' system! 'gem install bundler --conservative' system('bundle check') || system!('bundle install') -<% if options[:yarn] %> - system! 'bin/yarn' - -<% end %> <% unless options.skip_active_record -%> puts "\n== Updating database ==" system! 'bin/rails db:migrate' diff --git a/railties/lib/rails/generators/rails/app/templates/bin/yarn b/railties/lib/rails/generators/rails/app/templates/bin/yarn index cf48e2e5b7..872438cecb 100644 --- a/railties/lib/rails/generators/rails/app/templates/bin/yarn +++ b/railties/lib/rails/generators/rails/app/templates/bin/yarn @@ -1,2 +1,9 @@ VENDOR_PATH = File.expand_path('../vendor', __dir__) -Dir.chdir(VENDOR_PATH) { exec "yarnpkg #{ARGV.join(" ")}" } +Dir.chdir(VENDOR_PATH) do + begin + exec "yarnpkg #{ARGV.join(" ")}" + rescue Errno::ENOENT + puts "Yarn executable was not detected in the system." + puts "Download Yarn at https://yarnpkg.com/en/docs/install" + end +end 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/initializers/assets.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt index bf5399ac2d..f5d03fb117 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt @@ -3,9 +3,9 @@ # Version of your assets, change this if you want to expire all your assets. Rails.application.config.assets.version = '1.0' -# Add additional assets to the asset load path +# Add additional assets to the asset load path. # Rails.application.config.assets.paths << Emoji.images_path -<%- if options[:yarn] -%> +<%- unless options[:skip_yarn] -%> # Add Yarn node_modules folder to the asset load path. Rails.application.config.assets.paths << Rails.root.join('vendor/node_modules') <%- end -%> diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults.rb.tt index 3ad3eba98a..bd844f0503 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults.rb.tt @@ -24,9 +24,6 @@ ActiveSupport.to_time_preserves_timezone = <%= options[:update] ? false : true % # Require `belongs_to` associations by default. Previous versions had false. Rails.application.config.active_record.belongs_to_required_by_default = <%= options[:update] ? false : true %> <%- end -%> - -# Do not halt callback chains when a callback returns false. Previous versions had true. -ActiveSupport.halt_callback_chains_on_return_false = <%= options[:update] ? true : false %> <%- unless options[:update] -%> # Configure SSL options to enable HSTS with subdomains. Previous versions had false. 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/config/puma.rb b/railties/lib/rails/generators/rails/app/templates/config/puma.rb index 7ee948002e..1e19380dcb 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/puma.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/puma.rb @@ -1,13 +1,13 @@ # Puma can serve each request in a thread from an internal thread pool. -# The `threads` method setting takes two numbers a minimum and maximum. +# The `threads` method setting takes two numbers: a minimum and maximum. # Any libraries that use thread pools should be configured to match # the maximum value specified for Puma. Default is set to 5 threads for minimum -# and maximum, this matches the default thread size of Active Record. +# and maximum; this matches the default thread size of Active Record. # threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } threads threads_count, threads_count -# Specifies the `port` that Puma will listen on to receive requests, default is 3000. +# Specifies the `port` that Puma will listen on to receive requests; default is 3000. # port ENV.fetch("PORT") { 3000 } @@ -42,9 +42,9 @@ environment ENV.fetch("RAILS_ENV") { "development" } # The code in the `on_worker_boot` will be called if you are using # clustered mode by specifying a number of `workers`. After each worker -# process is booted this block will be run, if you are using `preload_app!` -# option you will want to use this block to reconnect to any threads -# or connections that may have been created at application boot, Ruby +# process is booted, this block will be run. If you are using the `preload_app!` +# option, you will want to use this block to reconnect to any threads +# or connections that may have been created at application boot, as Ruby # cannot share connections between processes. # # on_worker_boot do diff --git a/railties/lib/rails/generators/rails/app/templates/gitignore b/railties/lib/rails/generators/rails/app/templates/gitignore index dee42ebb87..1768b700d9 100644 --- a/railties/lib/rails/generators/rails/app/templates/gitignore +++ b/railties/lib/rails/generators/rails/app/templates/gitignore @@ -21,8 +21,9 @@ !/tmp/.keep <% end -%> -<% if options[:yarn] -%> +<% unless options[:skip_yarn] -%> /vendor/node_modules +/vendor/yarn-error.log <% end -%> .byebug_history diff --git a/railties/lib/rails/generators/rails/assets/assets_generator.rb b/railties/lib/rails/generators/rails/assets/assets_generator.rb index 265dada2ca..95d00c2d39 100644 --- a/railties/lib/rails/generators/rails/assets/assets_generator.rb +++ b/railties/lib/rails/generators/rails/assets/assets_generator.rb @@ -7,7 +7,7 @@ module Rails class_option :javascript_engine, desc: "Engine for JavaScripts" class_option :stylesheet_engine, desc: "Engine for Stylesheets" - protected + private def asset_name file_name diff --git a/railties/lib/rails/generators/rails/controller/controller_generator.rb b/railties/lib/rails/generators/rails/controller/controller_generator.rb index ced3c85c00..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)[2..-1] + 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 + 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/generator/generator_generator.rb b/railties/lib/rails/generators/rails/generator/generator_generator.rb index 8040ec5e7b..299a7da5f1 100644 --- a/railties/lib/rails/generators/rails/generator/generator_generator.rb +++ b/railties/lib/rails/generators/rails/generator/generator_generator.rb @@ -12,7 +12,7 @@ module Rails hook_for :test_framework - protected + private def generator_dir if options[:namespace] diff --git a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb index 2186fa4ded..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! @@ -286,7 +282,7 @@ task default: :test @namespaced_name ||= name.tr("-", "/") end - protected + private def create_dummy_app(path = nil) dummy_path(path) if path @@ -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 a28977319a..e7cb722473 100644 --- a/railties/lib/rails/generators/resource_helpers.rb +++ b/railties/lib/rails/generators/resource_helpers.rb @@ -23,10 +23,14 @@ module Rails assign_controller_names!(controller_name.pluralize) 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 :controller_name, :controller_file_name + private + def controller_class_path if options[:model_name] @controller_class_path @@ -55,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/generators/test_unit/generator/generator_generator.rb b/railties/lib/rails/generators/test_unit/generator/generator_generator.rb index 59f8d40343..6b6e094453 100644 --- a/railties/lib/rails/generators/test_unit/generator/generator_generator.rb +++ b/railties/lib/rails/generators/test_unit/generator/generator_generator.rb @@ -12,7 +12,7 @@ module TestUnit # :nodoc: template "generator_test.rb", File.join("test/lib/generators", class_path, "#{file_name}_generator_test.rb") end - protected + private def generator_path if options[:namespace] diff --git a/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb b/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb index 806279788e..67bff8e4f9 100644 --- a/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb +++ b/railties/lib/rails/generators/test_unit/mailer/mailer_generator.rb @@ -17,7 +17,7 @@ module TestUnit # :nodoc: template "preview.rb", File.join("test/mailers/previews", class_path, "#{file_name}_mailer_preview.rb") end - protected + private def file_name @_file_name ||= super.gsub(/_mailer/i, "") end diff --git a/railties/lib/rails/generators/testing/behaviour.rb b/railties/lib/rails/generators/testing/behaviour.rb index a1e5a233b9..64d641d096 100644 --- a/railties/lib/rails/generators/testing/behaviour.rb +++ b/railties/lib/rails/generators/testing/behaviour.rb @@ -82,23 +82,23 @@ module Rails Rails::Generators::GeneratedAttribute.parse([name, attribute_type, index].compact.join(":")) end - protected + private - def destination_root_is_set? # :nodoc: + def destination_root_is_set? raise "You need to configure your Rails::Generators::TestCase destination root." unless destination_root end - def ensure_current_path # :nodoc: + def ensure_current_path cd current_path end # Clears all files and directories in destination. - def prepare_destination + def prepare_destination # :doc: rm_rf(destination_root) mkdir_p(destination_root) end - def migration_file_name(relative) # :nodoc: + def migration_file_name(relative) absolute = File.expand_path(relative, destination_root) dirname, file_name = File.dirname(absolute), File.basename(absolute).sub(/\.rb$/, "") Dir.glob("#{dirname}/[0-9]*_*.rb").grep(/\d+_#{file_name}.rb$/).first 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/mailers_controller.rb b/railties/lib/rails/mailers_controller.rb index 95de998208..000ce40fbc 100644 --- a/railties/lib/rails/mailers_controller.rb +++ b/railties/lib/rails/mailers_controller.rb @@ -40,12 +40,12 @@ class Rails::MailersController < Rails::ApplicationController # :nodoc: end end - protected - def show_previews? + private + def show_previews? # :doc: ActionMailer::Base.show_previews end - def find_preview + def find_preview # :doc: candidates = [] params[:path].to_s.scan(%r{/|$}) { candidates << $` } preview = candidates.detect { |candidate| ActionMailer::Preview.exists?(candidate) } @@ -57,7 +57,7 @@ class Rails::MailersController < Rails::ApplicationController # :nodoc: end end - def find_preferred_part(*formats) + def find_preferred_part(*formats) # :doc: formats.each do |format| if part = @email.find_first_mime_type(format) return part @@ -69,7 +69,7 @@ class Rails::MailersController < Rails::ApplicationController # :nodoc: end end - def find_part(format) + def find_part(format) # :doc: if part = @email.find_first_mime_type(format) part elsif @email.mime_type == format 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 e3fee75603..853fc26051 100644 --- a/railties/lib/rails/rack/logger.rb +++ b/railties/lib/rails/rack/logger.rb @@ -27,45 +27,43 @@ module Rails end end - protected + private - def call_app(request, env) - 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) - '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) - @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 - - private def finish(request) instrumenter = ActiveSupport::Notifications.instrumenter diff --git a/railties/lib/rails/railtie.rb b/railties/lib/rails/railtie.rb index 696db61f01..474118c0a9 100644 --- a/railties/lib/rails/railtie.rb +++ b/railties/lib/rails/railtie.rb @@ -173,8 +173,8 @@ module Rails instance.configure(&block) end - protected - def generate_railtie_name(string) #:nodoc: + private + def generate_railtie_name(string) ActiveSupport::Inflector.underscore(string).tr("/", "_") end @@ -188,7 +188,6 @@ module Rails end end - private # receives an instance variable identifier, set the variable value if is # blank and append given block to value, which will be used later in # `#each_registered_block(type, &block)` diff --git a/railties/lib/rails/railtie/configurable.rb b/railties/lib/rails/railtie/configurable.rb index 39f1f87575..2a8295426e 100644 --- a/railties/lib/rails/railtie/configurable.rb +++ b/railties/lib/rails/railtie/configurable.rb @@ -24,7 +24,7 @@ module Rails class_eval(&block) end - protected + private def method_missing(*args, &block) instance.send(*args, &block) diff --git a/railties/lib/rails/source_annotation_extractor.rb b/railties/lib/rails/source_annotation_extractor.rb index 967e969f81..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 @@ -116,7 +116,7 @@ class SourceAnnotationExtractor # Otherwise it returns an empty hash. def extract_annotations_from(file, pattern) lineno = 0 - result = File.readlines(file).inject([]) do |list, line| + result = File.readlines(file, encoding: Encoding::BINARY).inject([]) do |list, line| lineno += 1 next list unless line =~ pattern list << Annotation.new(lineno, $1, $2) 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..87476b1b8c --- /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/assets_test.rb b/railties/test/application/assets_test.rb index edd43503bf..f38cacd6da 100644 --- a/railties/test/application/assets_test.rb +++ b/railties/test/application/assets_test.rb @@ -384,7 +384,7 @@ module ApplicationTests get "/assets/demo.js" assert_match "alert()", last_response.body - assert_equal nil, last_response.headers["Set-Cookie"] + assert_nil last_response.headers["Set-Cookie"] end test "files in any assets/ directories are not added to Sprockets" do 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 13fc98f0d6..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 @@ -28,30 +27,16 @@ module ApplicationTests assert_equal 3, x.payment_processing.retries assert_equal true, x.super_debugger assert_equal false, x.hyper_debugger - assert_equal nil, x.nil_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 c409f1ea79..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" @@ -614,7 +606,7 @@ module ApplicationTests app "development" assert_equal "b3c631c314c0bbca50c1b2843150fe33", app.config.secret_token - assert_equal nil, app.secrets.secret_key_base + assert_nil app.secrets.secret_key_base assert_equal app.key_generator.class, ActiveSupport::LegacyKeyGenerator end @@ -630,10 +622,11 @@ module ApplicationTests app "development" assert_equal "", app.config.secret_token - assert_equal nil, app.secrets.secret_key_base - assert_raise ArgumentError, /\AA secret is required/ do + assert_nil app.secrets.secret_key_base + 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 @@ -1204,7 +1198,7 @@ module ApplicationTests application.config.session_store :disabled end - assert_equal nil, app.config.session_store + assert_nil app.config.session_store end test "default session store initializer sets session store to cookie store" do diff --git a/railties/test/application/generators_test.rb b/railties/test/application/generators_test.rb index 0153f94504..d2ce14f594 100644 --- a/railties/test/application/generators_test.rb +++ b/railties/test/application/generators_test.rb @@ -169,5 +169,20 @@ module ApplicationTests assert File.exist?(File.join(rails_root, "app/views/notifier_mailer/foo.text.erb")) assert File.exist?(File.join(rails_root, "app/views/notifier_mailer/foo.html.erb")) end + + test "ARGV is mutated as expected" do + require "#{app_path}/config/environment" + Rails::Command.const_set("APP_PATH", "rails/all") + + FileUtils.cd(rails_root) do + ARGV = ["mailer", "notifier", "foo"] + Rails::Command.const_set("ARGV", ARGV) + quietly { Rails::Command.invoke :generate, ARGV } + + assert_equal ["notifier", "foo"], ARGV + end + + Rails::Command.send(:remove_const, "APP_PATH") + end end end 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/loading_test.rb b/railties/test/application/loading_test.rb index 38f96f3ab9..c75a25bc6f 100644 --- a/railties/test/application/loading_test.rb +++ b/railties/test/application/loading_test.rb @@ -357,7 +357,7 @@ class LoadingTest < ActiveSupport::TestCase assert Rails.application.initialized? end - protected + private def setup_ar! ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:") 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/session_test.rb b/railties/test/application/middleware/session_test.rb index 0e4acfdcec..959a629ede 100644 --- a/railties/test/application/middleware/session_test.rb +++ b/railties/test/application/middleware/session_test.rb @@ -173,7 +173,7 @@ module ApplicationTests secret = app.key_generator.generate_key("encrypted cookie") sign_secret = app.key_generator.generate_key("signed encrypted cookie") - encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret) + encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret) get "/foo/read_raw_cookie" assert_equal 1, encryptor.decrypt_and_verify(last_response.body)["foo"] @@ -222,7 +222,7 @@ module ApplicationTests secret = app.key_generator.generate_key("encrypted cookie") sign_secret = app.key_generator.generate_key("signed encrypted cookie") - encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret) + encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret) get "/foo/read_raw_cookie" assert_equal 1, encryptor.decrypt_and_verify(last_response.body)["foo"] @@ -281,7 +281,7 @@ module ApplicationTests secret = app.key_generator.generate_key("encrypted cookie") sign_secret = app.key_generator.generate_key("signed encrypted cookie") - encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret) + encryptor = ActiveSupport::MessageEncryptor.new(secret[0, ActiveSupport::MessageEncryptor.key_len], sign_secret) get "/foo/read_raw_cookie" assert_equal 2, encryptor.decrypt_and_verify(last_response.body)["foo"] diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb index 9baf7360a5..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! @@ -246,7 +277,7 @@ module ApplicationTests get "/", {}, "HTTP_IF_NONE_MATCH" => etag assert_equal 304, last_response.status assert_equal "", last_response.body - assert_equal nil, last_response.headers["Content-Type"] + assert_nil last_response.headers["Content-Type"] assert_equal "max-age=0, private, must-revalidate", last_response.headers["Cache-Control"] assert_equal etag, last_response.headers["Etag"] @@ -255,7 +286,7 @@ module ApplicationTests assert_equal "", last_response.body assert_equal "text/plain; charset=utf-8", last_response.headers["Content-Type"] assert_equal "no-cache", last_response.headers["Cache-Control"] - assert_equal nil, last_response.headers["Etag"] + assert_nil last_response.headers["Etag"] end test "ORIGINAL_FULLPATH is passed to env" do diff --git a/railties/test/application/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb index 51db634b75..c63f23fa0a 100644 --- a/railties/test/application/rake/dbs_test.rb +++ b/railties/test/application/rake/dbs_test.rb @@ -1,5 +1,4 @@ require "isolation/abstract_unit" -require "active_support/core_ext/string/strip" module ApplicationTests module RakeTests @@ -175,7 +174,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 +202,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/framework_test.rb b/railties/test/application/rake/framework_test.rb index 7ac37b7700..40488a6aab 100644 --- a/railties/test/application/rake/framework_test.rb +++ b/railties/test/application/rake/framework_test.rb @@ -1,5 +1,4 @@ require "isolation/abstract_unit" -require "active_support/core_ext/string/strip" module ApplicationTests module RakeTests 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 cd09270df1..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 @@ -387,14 +359,14 @@ module ApplicationTests bin/rails generate model product name:string; bin/rails db:migrate db:schema:cache:dump` end - assert File.exist?(File.join(app_path, "db", "schema_cache.dump")) + assert File.exist?(File.join(app_path, "db", "schema_cache.yml")) end def test_rake_clear_schema_cache Dir.chdir(app_path) do `bin/rails db:schema:cache:dump db:schema:cache:clear` end - assert !File.exist?(File.join(app_path, "db", "schema_cache.dump")) + assert !File.exist?(File.join(app_path, "db", "schema_cache.yml")) end def test_copy_templates 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 391886bf33..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_equal 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/actions_test.rb b/railties/test/generators/actions_test.rb index 0a26897a4d..360e8e97d7 100644 --- a/railties/test/generators/actions_test.rb +++ b/railties/test/generators/actions_test.rb @@ -369,7 +369,7 @@ F assert_equal("", action(:log, :yes, "YES")) end - protected + private def action(*args, &block) capture(:stdout) { generator.send(*args, &block) } diff --git a/railties/test/generators/api_app_generator_test.rb b/railties/test/generators/api_app_generator_test.rb index 7069a07bbe..c54d9cc599 100644 --- a/railties/test/generators/api_app_generator_test.rb +++ b/railties/test/generators/api_app_generator_test.rb @@ -105,10 +105,10 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase %w(app/assets app/helpers app/views/layouts/application.html.erb + bin/yarn config/initializers/assets.rb config/initializers/cookies_serializer.rb lib/assets - vendor/assets test/helpers tmp/cache/assets public/404.html @@ -116,6 +116,8 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase public/500.html public/apple-touch-icon-precomposed.png public/apple-touch-icon.png - public/favicon.ico) + public/favicon.icon + vendor/package.json + ) end end diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index 15a923676d..ce29d93d6e 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -43,9 +43,6 @@ DEFAULT_APP_FILES = %w( test/mailers test/integration vendor - vendor/assets - vendor/assets/stylesheets - vendor/assets/javascripts tmp tmp/cache tmp/cache/assets @@ -211,7 +208,6 @@ class AppGeneratorTest < Rails::Generators::TestCase quietly { generator.send(:update_config_files) } assert_file "#{app_root}/config/initializers/new_framework_defaults.rb" do |content| - assert_match(/ActiveSupport\.halt_callback_chains_on_return_false = true/, content) assert_match(/Rails\.application\.config.active_record\.belongs_to_required_by_default = false/, content) assert_no_match(/Rails\.application\.config\.ssl_options/, content) end @@ -422,6 +418,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["']/ @@ -466,7 +469,6 @@ class AppGeneratorTest < Rails::Generators::TestCase run_generator [destination_root, "--skip-javascript"] assert_no_file "app/assets/javascripts" - assert_no_file "vendor/assets/javascripts" assert_file "app/views/layouts/application.html.erb" do |contents| assert_match(/stylesheet_link_tag\s+'application', media: 'all' %>/, contents) @@ -492,12 +494,26 @@ class AppGeneratorTest < Rails::Generators::TestCase end end - def test_generator_if_yarn_option_is_given - run_generator([destination_root, "--yarn"]) + def test_generator_for_yarn + run_generator([destination_root]) assert_file "vendor/package.json", /dependencies/ assert_file "config/initializers/assets.rb", /node_modules/ end + def test_generator_for_yarn_skipped + run_generator([destination_root, "--skip-yarn"]) + assert_no_file "vendor/package.json" + + 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 run_generator assert_gem "jbuilder" @@ -580,6 +596,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 @@ -618,10 +635,6 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_generates_with_bundler end - def test_generation_runs_yarn_install_with_yarn_option - assert_generates_with_yarn yarn: true - end - def test_dev_option assert_generates_with_bundler dev: true rails_path = File.expand_path("../../..", Rails.root) @@ -723,6 +736,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( @@ -740,7 +758,6 @@ class AppGeneratorTest < Rails::Generators::TestCase test/helpers test/integration tmp - vendor/assets/stylesheets ) folders_with_keep.each do |folder| assert_file("#{folder}/.keep") @@ -770,7 +787,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,10 +802,10 @@ class AppGeneratorTest < Rails::Generators::TestCase end end - assert_equal 3, @sequence_step + assert_equal 4, @sequence_step end - protected + private def stub_rails_application(root) Rails.application.config.root = root @@ -847,18 +864,4 @@ class AppGeneratorTest < Rails::Generators::TestCase quietly { generator.invoke_all } end end - - def assert_generates_with_yarn(options = {}) - generator([destination_root], options) - - command_check = -> command do - @install_called ||= 0 - @install_called += 1 - assert_equal 1, @install_called, "install expected to be called once, but was called #{@install_called} times" - end - - generator.stub :yarn_command, command_check do - quietly { generator.invoke_all } - end - end end 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/named_base_test.rb b/railties/test/generators/named_base_test.rb index 0258b3b9d7..3015b5363b 100644 --- a/railties/test/generators/named_base_test.rb +++ b/railties/test/generators/named_base_test.rb @@ -131,7 +131,7 @@ class NamedBaseTest < Rails::Generators::TestCase assert_name g, "admin.foos", :controller_i18n_scope end - protected + private def assert_name(generator, value, method) assert_equal value, generator.send(method) diff --git a/railties/test/generators/plugin_generator_test.rb b/railties/test/generators/plugin_generator_test.rb index a0018dc782..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 @@ -714,7 +715,7 @@ class PluginGeneratorTest < Rails::Generators::TestCase end end - protected + private def action(*args, &block) silence(:stdout) { generator.send(*args, &block) } 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/rails_info_test.rb b/railties/test/rails_info_test.rb index 59e79de41a..9f4c5bb025 100644 --- a/railties/test/rails_info_test.rb +++ b/railties/test/rails_info_test.rb @@ -54,7 +54,7 @@ class InfoTest < ActiveSupport::TestCase end end - protected + private def properties Rails::Info.properties 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 diff --git a/tools/test.rb b/tools/test.rb index 824ee57c96..ce546b382d 100644 --- a/tools/test.rb +++ b/tools/test.rb @@ -4,6 +4,8 @@ require "bundler" Bundler.setup require "rails/test_unit/minitest_plugin" +require "rails/test_unit/line_filtering" +require "active_support/test_case" module Rails # Necessary to get rerun-snippts working. @@ -12,6 +14,7 @@ module Rails end end +ActiveSupport::TestCase.extend Rails::LineFiltering Rails::TestUnitReporter.executable = "bin/test" Minitest.run_via[:rails] = true require "active_support/testing/autorun" |